mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Fixing login cookie max-age.
This commit is contained in:
parent
8894fbf98b
commit
eeca4e4103
7 changed files with 66 additions and 41 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const signInEmailDto = z.object({
|
export const signinUsernameDto = z.object({
|
||||||
username: z
|
username: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
|
|
@ -9,4 +9,4 @@ export const signInEmailDto = z.object({
|
||||||
password: z.string({ required_error: 'Password is required' }).trim(),
|
password: z.string({ required_error: 'Password is required' }).trim(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type SignInEmailDto = z.infer<typeof signInEmailDto>;
|
export type SigninUsernameDto = z.infer<typeof signinUsernameDto>;
|
||||||
|
|
@ -1,27 +1,43 @@
|
||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
|
import { setCookie } from 'hono/cookie';
|
||||||
import { zValidator } from '@hono/zod-validator';
|
import { zValidator } from '@hono/zod-validator';
|
||||||
import { inject, injectable } from 'tsyringe';
|
import { inject, injectable } from 'tsyringe';
|
||||||
|
import { TimeSpan } from 'oslo';
|
||||||
import type { HonoTypes } from '../types';
|
import type { HonoTypes } from '../types';
|
||||||
import { limiter } from '../middleware/rate-limiter.middleware';
|
import { limiter } from '../middleware/rate-limiter.middleware';
|
||||||
import type { Controller } from '../interfaces/controller.interface';
|
import type { Controller } from '../interfaces/controller.interface';
|
||||||
import { signInEmailDto } from '$lib/dtos/signin-email.dto';
|
|
||||||
import { LoginRequestsService } from '../services/loginrequest.service';
|
import { LoginRequestsService } from '../services/loginrequest.service';
|
||||||
|
import { signinUsernameDto } from "$lib/dtos/signin-username.dto";
|
||||||
|
import {LuciaProvider} from "$lib/server/api/providers";
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class LoginController implements Controller {
|
export class LoginController implements Controller {
|
||||||
controller = new Hono<HonoTypes>();
|
controller = new Hono<HonoTypes>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject(LoginRequestsService) private readonly loginRequestsService: LoginRequestsService
|
@inject(LoginRequestsService) private readonly loginRequestsService: LoginRequestsService,
|
||||||
|
@inject(LuciaProvider) private lucia: LuciaProvider
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
routes() {
|
routes() {
|
||||||
return this.controller
|
return this.controller
|
||||||
.post('/', zValidator('json', signInEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
.post('/', zValidator('json', signinUsernameDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||||
const { username, password } = c.req.valid('json');
|
const { username, password } = c.req.valid('json');
|
||||||
await this.loginRequestsService.verify({ username, password }, c.req);
|
const session = await this.loginRequestsService.verify({ username, password }, c.req);
|
||||||
return c.json({ message: 'Verification email sent' });
|
const sessionCookie = this.lucia.createSessionCookie(session.id);
|
||||||
|
console.log("set cookie", sessionCookie);
|
||||||
|
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||||
|
path: sessionCookie.attributes.path,
|
||||||
|
maxAge: sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds()
|
||||||
|
? sessionCookie.attributes.maxAge : new TimeSpan(2, 'w').seconds(),
|
||||||
|
domain: sessionCookie.attributes.domain,
|
||||||
|
sameSite: sessionCookie.attributes.sameSite as any,
|
||||||
|
secure: sessionCookie.attributes.secure,
|
||||||
|
httpOnly: sessionCookie.attributes.httpOnly,
|
||||||
|
expires: sessionCookie.attributes.expires
|
||||||
|
});
|
||||||
|
return c.json({ message: 'ok' });
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export const lucia = new Lucia(adapter, {
|
||||||
...attributes,
|
...attributes,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
sessionExpiresIn: new TimeSpan(30, 'd'), // 30 days
|
sessionExpiresIn: new TimeSpan(2, 'w'), // 2 weeks
|
||||||
sessionCookie: {
|
sessionCookie: {
|
||||||
name: 'session',
|
name: 'session',
|
||||||
expires: false, // session cookies have very long lifespan (2 years)
|
expires: false, // session cookies have very long lifespan (2 years)
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import { MailerService } from './mailer.service';
|
||||||
import { TokensService } from './tokens.service';
|
import { TokensService } from './tokens.service';
|
||||||
import { LuciaProvider } from '../providers/lucia.provider';
|
import { LuciaProvider } from '../providers/lucia.provider';
|
||||||
import { UsersRepository } from '../repositories/users.repository';
|
import { UsersRepository } from '../repositories/users.repository';
|
||||||
import type { SignInEmailDto } from '../../../dtos/signin-email.dto';
|
|
||||||
import { CredentialsRepository } from '../repositories/credentials.repository';
|
import { CredentialsRepository } from '../repositories/credentials.repository';
|
||||||
import type { HonoRequest } from 'hono';
|
import type { HonoRequest } from 'hono';
|
||||||
|
import type {SigninUsernameDto} from "$lib/dtos/signin-username.dto";
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class LoginRequestsService {
|
export class LoginRequestsService {
|
||||||
|
|
@ -32,7 +32,7 @@ export class LoginRequestsService {
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
async verify(data: SignInEmailDto, req: HonoRequest) {
|
async verify(data: SigninUsernameDto, req: HonoRequest) {
|
||||||
const requestIpAddress = req.header('x-real-ip');
|
const requestIpAddress = req.header('x-real-ip');
|
||||||
const requestIpCountry = req.header('x-vercel-ip-country');
|
const requestIpCountry = req.header('x-vercel-ip-country');
|
||||||
const existingUser = await this.usersRepository.findOneByUsername(data.username);
|
const existingUser = await this.usersRepository.findOneByUsername(data.username);
|
||||||
|
|
@ -47,7 +47,7 @@ export class LoginRequestsService {
|
||||||
throw BadRequest('Invalid credentials');
|
throw BadRequest('Invalid credentials');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await this.tokensService.verifyHashedToken(credential.hashedPassword, data.password)) {
|
if (!await this.tokensService.verifyHashedToken(credential.secret_data, data.password)) {
|
||||||
throw BadRequest('Invalid credentials');
|
throw BadRequest('Invalid credentials');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,15 +58,15 @@ export class LoginRequestsService {
|
||||||
ip_address: requestIpAddress || 'unknown',
|
ip_address: requestIpAddress || 'unknown',
|
||||||
twoFactorAuthEnabled:
|
twoFactorAuthEnabled:
|
||||||
!!totpCredentials &&
|
!!totpCredentials &&
|
||||||
totpCredentials?.secret !== null &&
|
totpCredentials?.secret_data !== null &&
|
||||||
totpCredentials?.secret !== '',
|
totpCredentials?.secret_data !== '',
|
||||||
isTwoFactorAuthenticated: false,
|
isTwoFactorAuthenticated: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new user and send a welcome email - or other onboarding process
|
// Create a new user and send a welcome email - or other onboarding process
|
||||||
private async handleNewUserRegistration(email: string) {
|
private async handleNewUserRegistration(email: string) {
|
||||||
const newUser = await this.usersRepository.create({ email, verified: true, avatar: null })
|
const newUser = await this.usersRepository.create({ email, verified: true })
|
||||||
this.mailerService.sendWelcome({ to: email, props: null });
|
this.mailerService.sendWelcome({ to: email, props: null });
|
||||||
// TODO: add whatever onboarding process or extra data you need here
|
// TODO: add whatever onboarding process or extra data you need here
|
||||||
return newUser
|
return newUser
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { fail, error, type Actions } from '@sveltejs/kit';
|
import { fail, type Actions } from '@sveltejs/kit';
|
||||||
import { eq, or } from 'drizzle-orm';
|
import { eq, or } from 'drizzle-orm';
|
||||||
import { Argon2id } from 'oslo/password';
|
import { Argon2id } from 'oslo/password';
|
||||||
import { zod } from 'sveltekit-superforms/adapters';
|
import { zod } from 'sveltekit-superforms/adapters';
|
||||||
|
|
@ -7,46 +7,57 @@ import { redirect } from 'sveltekit-flash-message/server';
|
||||||
import { RateLimiter } from 'sveltekit-rate-limiter/server';
|
import { RateLimiter } from 'sveltekit-rate-limiter/server';
|
||||||
import db from '../../../db';
|
import db from '../../../db';
|
||||||
import { lucia } from '$lib/server/auth';
|
import { lucia } from '$lib/server/auth';
|
||||||
import { signInSchema } from '$lib/validations/auth';
|
|
||||||
import { twoFactor, usersTable, type Users } from '$db/schema';
|
import { twoFactor, usersTable, type Users } from '$db/schema';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils';
|
import {signinUsernameDto} from "$lib/dtos/signin-username.dto";
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
export const load: PageServerLoad = async (event) => {
|
||||||
const { locals, cookies } = event;
|
const { locals } = event;
|
||||||
const { user, session } = event.locals;
|
|
||||||
|
|
||||||
if (userFullyAuthenticated(user, session)) {
|
const authedUser = await locals.getAuthedUser();
|
||||||
|
|
||||||
|
if (authedUser) {
|
||||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||||
throw redirect('/', message, event);
|
throw redirect('/', message, event);
|
||||||
} else if (userNotFullyAuthenticated(user, session)) {
|
|
||||||
await lucia.invalidateSession(locals.session!.id!);
|
|
||||||
const sessionCookie = lucia.createBlankSessionCookie();
|
|
||||||
cookies.set(sessionCookie.name, sessionCookie.value, {
|
|
||||||
path: '.',
|
|
||||||
...sessionCookie.attributes,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
const form = await superValidate(event, zod(signInSchema));
|
|
||||||
|
// if (userFullyAuthenticated(user, session)) {
|
||||||
|
// const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||||
|
// throw redirect('/', message, event);
|
||||||
|
// } else if (userNotFullyAuthenticated(user, session)) {
|
||||||
|
// await lucia.invalidateSession(locals.session!.id!);
|
||||||
|
// const sessionCookie = lucia.createBlankSessionCookie();
|
||||||
|
// cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||||
|
// path: '.',
|
||||||
|
// ...sessionCookie.attributes,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
const form = await superValidate(event, zod(signinUsernameDto));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
form,
|
form,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const limiter = new RateLimiter({
|
|
||||||
// A rate is defined by [number, unit]
|
|
||||||
IPUA: [5, 'm'],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
default: async (event) => {
|
default: async (event) => {
|
||||||
if (await limiter.isLimited(event)) {
|
// if (await limiter.isLimited(event)) {
|
||||||
throw error(429);
|
// throw error(429);
|
||||||
}
|
// }
|
||||||
|
|
||||||
const { locals } = event;
|
const { locals } = event;
|
||||||
const form = await superValidate(event, zod(signInSchema));
|
|
||||||
|
const authedUser = await locals.getAuthedUser();
|
||||||
|
|
||||||
|
if (authedUser) {
|
||||||
|
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||||
|
throw redirect('/', message, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = await superValidate(event, zod(signinUsernameDto));
|
||||||
|
|
||||||
|
const { error } = await locals.api.login.$post({ json: form.data }).then(locals.parseApiResponse);
|
||||||
|
if (error) return setError(form, 'username', error);
|
||||||
|
|
||||||
if (!form.valid) {
|
if (!form.valid) {
|
||||||
form.data.password = '';
|
form.data.password = '';
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@
|
||||||
<form method="POST" use:enhance>
|
<form method="POST" use:enhance>
|
||||||
<Form.Field form={superLoginForm} name="username">
|
<Form.Field form={superLoginForm} name="username">
|
||||||
<Form.Control let:attrs>
|
<Form.Control let:attrs>
|
||||||
<Form.Label for="username">Username/Email</Form.Label>
|
<Form.Label for="username">Username</Form.Label>
|
||||||
<Input {...attrs} autocomplete="username" bind:value={$loginForm.username} />
|
<Input {...attrs} autocomplete="username" bind:value={$loginForm.username} />
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.FieldErrors />
|
<Form.FieldErrors />
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import { add_user_to_role } from '$server/roles';
|
||||||
import db from '../../../db';
|
import db from '../../../db';
|
||||||
import { collections, usersTable, wishlists } from '$db/schema';
|
import { collections, usersTable, wishlists } from '$db/schema';
|
||||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||||
import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils';
|
|
||||||
|
|
||||||
const limiter = new RateLimiter({
|
const limiter = new RateLimiter({
|
||||||
// A rate is defined by [number, unit]
|
// A rate is defined by [number, unit]
|
||||||
|
|
@ -30,8 +29,7 @@ const signUpDefaults = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
export const load: PageServerLoad = async (event) => {
|
||||||
const { locals, cookies } = event;
|
const { locals } = event;
|
||||||
const { user, session } = event.locals;
|
|
||||||
|
|
||||||
const authedUser = await locals.getAuthedUser();
|
const authedUser = await locals.getAuthedUser();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue