@@ -212,11 +212,14 @@ export default defineAdder({
212
212
imports . addNamespace ( ast , '$lib/server/db/schema' , 'table' ) ;
213
213
imports . addNamed ( ast , '$lib/server/db' , { db : 'db' } ) ;
214
214
imports . addNamed ( ast , '@oslojs/encoding' , {
215
- encodeBase32LowerCaseNoPadding : 'encodeBase32LowerCaseNoPadding ' ,
215
+ encodeBase64url : 'encodeBase64url ' ,
216
216
encodeHexLowerCase : 'encodeHexLowerCase'
217
217
} ) ;
218
218
imports . addNamed ( ast , '@oslojs/crypto/sha2' , { sha256 : 'sha256' } ) ;
219
219
imports . addNamed ( ast , 'drizzle-orm' , { eq : 'eq' } ) ;
220
+ if ( typescript ) {
221
+ imports . addNamed ( ast , '@sveltejs/kit' , { RequestEvent : 'RequestEvent' } , true ) ;
222
+ }
220
223
221
224
const ms = new MagicString ( generateCode ( ) . trim ( ) ) ;
222
225
const [ ts ] = utils . createPrinter ( typescript ) ;
@@ -227,21 +230,22 @@ export default defineAdder({
227
230
if ( ! ms . original . includes ( 'export const sessionCookieName' ) ) {
228
231
ms . append ( "\n\nexport const sessionCookieName = 'auth-session';" ) ;
229
232
}
230
- if ( ! ms . original . includes ( 'function generateSessionToken' ) ) {
233
+ if ( ! ms . original . includes ( 'export function generateSessionToken' ) ) {
231
234
const generateSessionToken = dedent `
232
- ${ ts ( '' , '/** @returns {string} */' ) }
233
- function generateSessionToken()${ ts ( ': string' ) } {
234
- const bytes = crypto.getRandomValues(new Uint8Array(20));
235
- const token = encodeBase32LowerCaseNoPadding(bytes);
235
+ export function generateSessionToken() {
236
+ const bytes = crypto.getRandomValues(new Uint8Array(18));
237
+ const token = encodeBase64url(bytes);
236
238
return token;
237
239
}` ;
238
240
ms . append ( `\n\n${ generateSessionToken } ` ) ;
239
241
}
240
242
if ( ! ms . original . includes ( 'async function createSession' ) ) {
241
- const createSession = dedent `
242
- ${ ts ( '' , '/** @param {string} userId */' ) }
243
- export async function createSession(userId${ ts ( ': string' ) } )${ ts ( ': Promise<table.Session>' ) } {
244
- const token = generateSessionToken();
243
+ const createSession = dedent `
244
+ ${ ts ( '' , '/**' ) }
245
+ ${ ts ( '' , ' * @param {string} token' ) }
246
+ ${ ts ( '' , ' * @param {string} userId' ) }
247
+ ${ ts ( '' , ' */' ) }
248
+ export async function createSession(token${ ts ( ': string' ) } , userId${ ts ( ': string' ) } ) {
245
249
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
246
250
const session${ ts ( ': table.Session' ) } = {
247
251
id: sessionId,
@@ -253,21 +257,11 @@ export default defineAdder({
253
257
}` ;
254
258
ms . append ( `\n\n${ createSession } ` ) ;
255
259
}
256
- if ( ! ms . original . includes ( 'async function invalidateSession' ) ) {
257
- const invalidateSession = dedent `
258
- ${ ts ( '' , '/**' ) }
259
- ${ ts ( '' , ' * @param {string} sessionId' ) }
260
- ${ ts ( '' , ' * @returns {Promise<void>}' ) }
261
- ${ ts ( '' , ' */' ) }
262
- export async function invalidateSession(sessionId${ ts ( ': string' ) } )${ ts ( ': Promise<void>' ) } {
263
- await db.delete(table.session).where(eq(table.session.id, sessionId));
264
- }` ;
265
- ms . append ( `\n\n${ invalidateSession } ` ) ;
266
- }
267
- if ( ! ms . original . includes ( 'async function validateSession' ) ) {
268
- const validateSession = dedent `
269
- ${ ts ( '' , '/** @param {string} sessionId */' ) }
270
- export async function validateSession(sessionId${ ts ( ': string' ) } ) {
260
+ if ( ! ms . original . includes ( 'async function validateSessionToken' ) ) {
261
+ const validateSessionToken = dedent `
262
+ ${ ts ( '' , '/** @param {string} token */' ) }
263
+ export async function validateSessionToken(token${ ts ( ': string' ) } ) {
264
+ const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
271
265
const [result] = await db
272
266
.select({
273
267
// Adjust user table here to tweak returned data
@@ -300,14 +294,46 @@ export default defineAdder({
300
294
301
295
return { session, user };
302
296
}` ;
303
- ms . append ( `\n\n${ validateSession } ` ) ;
297
+ ms . append ( `\n\n${ validateSessionToken } ` ) ;
304
298
}
305
299
if ( typescript && ! ms . original . includes ( 'export type SessionValidationResult' ) ) {
306
300
const sessionType =
307
- 'export type SessionValidationResult = Awaited<ReturnType<typeof validateSession >>;' ;
301
+ 'export type SessionValidationResult = Awaited<ReturnType<typeof validateSessionToken >>;' ;
308
302
ms . append ( `\n\n${ sessionType } ` ) ;
309
303
}
310
-
304
+ if ( ! ms . original . includes ( 'async function invalidateSession' ) ) {
305
+ const invalidateSession = dedent `
306
+ ${ ts ( '' , '/** @param {string} sessionId */' ) }
307
+ export async function invalidateSession(sessionId${ ts ( ': string' ) } ) {
308
+ await db.delete(table.session).where(eq(table.session.id, sessionId));
309
+ }` ;
310
+ ms . append ( `\n\n${ invalidateSession } ` ) ;
311
+ }
312
+ if ( ! ms . original . includes ( 'export function setSessionTokenCookie' ) ) {
313
+ const setSessionTokenCookie = dedent `
314
+ ${ ts ( '' , '/**' ) }
315
+ ${ ts ( '' , ' * @param {import("@sveltejs/kit").RequestEvent} event' ) }
316
+ ${ ts ( '' , ' * @param {string} token' ) }
317
+ ${ ts ( '' , ' * @param {Date} expiresAt' ) }
318
+ ${ ts ( '' , ' */' ) }
319
+ export function setSessionTokenCookie(event${ ts ( ': RequestEvent' ) } , token${ ts ( ': string' ) } , expiresAt${ ts ( ': Date' ) } ) {
320
+ event.cookies.set(sessionCookieName, token, {
321
+ expires: expiresAt,
322
+ path: '/'
323
+ });
324
+ }` ;
325
+ ms . append ( `\n\n${ setSessionTokenCookie } ` ) ;
326
+ }
327
+ if ( ! ms . original . includes ( 'export function deleteSessionTokenCookie' ) ) {
328
+ const deleteSessionTokenCookie = dedent `
329
+ ${ ts ( '' , '/** @param {import("@sveltejs/kit").RequestEvent} event */' ) }
330
+ export function deleteSessionTokenCookie(event${ ts ( ': RequestEvent' ) } ) {
331
+ event.cookies.delete(sessionCookieName, {
332
+ path: '/'
333
+ });
334
+ }` ;
335
+ ms . append ( `\n\n${ deleteSessionTokenCookie } ` ) ;
336
+ }
311
337
return ms . toString ( ) ;
312
338
}
313
339
} ,
@@ -339,7 +365,6 @@ export default defineAdder({
339
365
content : ( { content, typescript } ) => {
340
366
const { ast, generateCode } = parseScript ( content ) ;
341
367
imports . addNamespace ( ast , '$lib/server/auth.js' , 'auth' ) ;
342
- imports . addNamed ( ast , '$app/environment' , { dev : 'dev' } ) ;
343
368
kit . addHooksHandle ( ast , typescript , 'handleAuth' , getAuthHandleContent ( ) ) ;
344
369
return generateCode ( ) ;
345
370
}
@@ -365,10 +390,9 @@ export default defineAdder({
365
390
const [ ts ] = utils . createPrinter ( typescript ) ;
366
391
return dedent `
367
392
import { hash, verify } from '@node-rs/argon2';
368
- import { generateRandomString } from '@oslojs/crypto/random ';
393
+ import { encodeBase64url } from '@oslojs/encoding ';
369
394
import { fail, redirect } from '@sveltejs/kit';
370
395
import { eq } from 'drizzle-orm';
371
- import { dev } from '$app/environment';
372
396
import * as auth from '$lib/server/auth';
373
397
import { db } from '$lib/server/db';
374
398
import * as table from '$lib/server/db/schema';
@@ -413,14 +437,9 @@ export default defineAdder({
413
437
return fail(400, { message: 'Incorrect username or password' });
414
438
}
415
439
416
- const session = await auth.createSession(existingUser.id);
417
- event.cookies.set(auth.sessionCookieName, session.id, {
418
- path: '/',
419
- sameSite: 'lax',
420
- httpOnly: true,
421
- expires: session.expiresAt,
422
- secure: !dev
423
- });
440
+ const sessionToken = auth.generateSessionToken();
441
+ const session = await auth.createSession(sessionToken, existingUser.id);
442
+ auth.setSessionTokenCookie(event, sessionToken, session.expiresAt);
424
443
425
444
return redirect(302, '/demo/lucia');
426
445
},
@@ -448,25 +467,21 @@ export default defineAdder({
448
467
try {
449
468
await db.insert(table.user).values({ id: userId, username, passwordHash });
450
469
451
- const session = await auth.createSession(userId);
452
- event.cookies.set(auth.sessionCookieName, session.id, {
453
- path: '/',
454
- sameSite: 'lax',
455
- httpOnly: true,
456
- expires: session.expiresAt,
457
- secure: !dev
458
- });
470
+ const sessionToken = auth.generateSessionToken();
471
+ const session = await auth.createSession(sessionToken, userId);
472
+ auth.setSessionTokenCookie(event, sessionToken, session.expiresAt);
459
473
} catch (e) {
460
474
return fail(500, { message: 'An error has occurred' });
461
475
}
462
476
return redirect(302, '/demo/lucia');
463
477
},
464
478
};
465
479
466
- const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_';
467
-
468
- function generateUserId(length = 21)${ ts ( ': string' ) } {
469
- return generateRandomString({ read: (bytes) => crypto.getRandomValues(bytes) }, alphabet, length);
480
+ function generateUserId() {
481
+ // ID with 120 bits of entropy, or about the same as UUID v4.
482
+ const bytes = crypto.getRandomValues(new Uint8Array(15));
483
+ const id = encodeBase64url(bytes);
484
+ return id;
470
485
}
471
486
472
487
function validateUsername(username${ ts ( ': unknown' ) } )${ ts ( ': username is string' ) } {
@@ -554,7 +569,7 @@ export default defineAdder({
554
569
return fail(401);
555
570
}
556
571
await auth.invalidateSession(event.locals.session.id);
557
- event.cookies.delete( auth.sessionCookieName, { path: '/' } );
572
+ auth.deleteSessionTokenCookie(event );
558
573
559
574
return redirect(302, '/demo/lucia/login');
560
575
},
@@ -636,24 +651,18 @@ function createLuciaType(name: string): AstTypes.TSInterfaceBody['body'][number]
636
651
function getAuthHandleContent ( ) {
637
652
return `
638
653
async ({ event, resolve }) => {
639
- const sessionId = event.cookies.get(auth.sessionCookieName);
640
- if (!sessionId ) {
654
+ const sessionToken = event.cookies.get(auth.sessionCookieName);
655
+ if (!sessionToken ) {
641
656
event.locals.user = null;
642
657
event.locals.session = null;
643
658
return resolve(event);
644
659
}
645
660
646
- const { session, user } = await auth.validateSession(sessionId );
661
+ const { session, user } = await auth.validateSessionToken(sessionToken );
647
662
if (session) {
648
- event.cookies.set(auth.sessionCookieName, session.id, {
649
- path: '/',
650
- sameSite: 'lax',
651
- httpOnly: true,
652
- expires: session.expiresAt,
653
- secure: !dev
654
- });
663
+ auth.setSessionTokenCookie(event, sessionToken, session.expiresAt);
655
664
} else {
656
- event.cookies.delete( auth.sessionCookieName, { path: '/' } );
665
+ auth.deleteSessionTokenCookie(event );
657
666
}
658
667
659
668
event.locals.user = user;
0 commit comments