diff --git a/src/app.d.ts b/src/app.d.ts index fe4ce28..183345d 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,6 +1,7 @@ import { ApiClient } from '$lib/server/api'; import type { User } from 'lucia'; -import {parseApiResponse} from '$lib/utils/api' +import { parseApiResponse } from '$lib/utils/api' +import type { Security } from '$lib/utils/security'; // See https://kit.svelte.dev/docs/types#app // for information about these interfaces @@ -13,17 +14,17 @@ declare global { getAuthedUser: () => Promise | null>; getAuthedUserOrThrow: () => Promise>; } - + // interface PageData {} // interface PageState {} // interface Platform {} namespace Superforms { - type Message = { - type: 'error' | 'success', - text: string - } - } + type Message = { + type: 'error' | 'success', + text: string + } + } } } -export {}; +export { }; diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 8c136d6..ac3a0f6 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -15,7 +15,6 @@ const apiClient: Handle = async ({ event, resolve }) => { } }); - /* ----------------------------- Auth functions ----------------------------- */ async function getAuthedUser() { const { data } = await api.iam.user.$get().then(parseApiResponse) diff --git a/src/lib/server/api/controllers/iam.controller.ts b/src/lib/server/api/controllers/iam.controller.ts index 4e4becd..cc73e15 100644 --- a/src/lib/server/api/controllers/iam.controller.ts +++ b/src/lib/server/api/controllers/iam.controller.ts @@ -5,13 +5,13 @@ import { IamService } from '../services/iam.service'; import { signInEmailDto } from '../../../dtos/signin-email.dto'; import { setCookie } from 'hono/cookie'; import { LuciaProvider } from '../providers/lucia.provider'; -import { requireAuth } from '../middleware/require-auth.middleware'; import { updateEmailDto } from '../../../dtos/update-email.dto'; import { verifyEmailDto } from '../../../dtos/verify-email.dto'; import { Hono } from 'hono'; import type { HonoTypes } from '../types'; import type { Controller } from '../interfaces/controller.interface'; import { limiter } from '../middleware/rate-limiter.middlware'; +import { requireAuth } from '../middleware/auth.middleware'; /* -------------------------------------------------------------------------- */ /* Controller */ diff --git a/src/lib/server/api/middleware/auth-session.middleware.ts b/src/lib/server/api/middleware/auth.middleware.ts similarity index 77% rename from src/lib/server/api/middleware/auth-session.middleware.ts rename to src/lib/server/api/middleware/auth.middleware.ts index 5a70a74..9c21ea8 100644 --- a/src/lib/server/api/middleware/auth-session.middleware.ts +++ b/src/lib/server/api/middleware/auth.middleware.ts @@ -3,6 +3,8 @@ import { createMiddleware } from 'hono/factory'; import type { HonoTypes } from '../types'; import { lucia } from '../infrastructure/auth/lucia'; import { verifyRequestOrigin } from 'lucia'; +import type { Session, User } from 'lucia'; +import { Unauthorized } from '../common/errors'; export const verifyOrigin: MiddlewareHandler = createMiddleware(async (c, next) => { if (c.req.method === "GET") { @@ -35,3 +37,14 @@ export const validateAuthSession: MiddlewareHandler = createMiddlewar c.set("user", user); return next(); }) + +export const requireAuth: MiddlewareHandler<{ + Variables: { + session: Session; + user: User; + }; +}> = createMiddleware(async (c, next) => { + const user = c.var.user; + if (!user) throw Unauthorized('You must be logged in to access this resource'); + return next(); +}); diff --git a/src/lib/server/api/middleware/rate-limiter.middlware.ts b/src/lib/server/api/middleware/rate-limiter.middlware.ts index 8615f29..f9321b7 100644 --- a/src/lib/server/api/middleware/rate-limiter.middlware.ts +++ b/src/lib/server/api/middleware/rate-limiter.middlware.ts @@ -5,18 +5,16 @@ import type { HonoTypes } from "../types"; const client = new RedisClient() -type LimiterProps = { +export function limiter({ limit, minutes, key = "" }: { limit: number; minutes: number; key?: string; -} - -export function limiter({limit, minutes, key = ""}: LimiterProps) { +}) { return rateLimiter({ windowMs: minutes * 60 * 1000, // every x minutes limit, // Limit each IP to 100 requests per `window` (here, per 15 minutes). standardHeaders: "draft-6", // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header - keyGenerator: (c) => { + keyGenerator: (c) => { const vars = c.var as HonoTypes['Variables']; const clientKey = vars.user?.id || c.req.header("x-forwarded-for"); const pathKey = key || c.req.routePath; diff --git a/src/lib/server/api/middleware/require-auth.middleware.ts b/src/lib/server/api/middleware/require-auth.middleware.ts deleted file mode 100644 index a53f9ca..0000000 --- a/src/lib/server/api/middleware/require-auth.middleware.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { MiddlewareHandler } from 'hono'; -import { createMiddleware } from 'hono/factory'; -import type { Session, User } from 'lucia'; -import { Unauthorized } from '../common/errors'; - -export const requireAuth: MiddlewareHandler<{ - Variables: { - session: Session; - user: User; - }; -}> = createMiddleware(async (c, next) => { - const user = c.var.user; - if (!user) throw Unauthorized('You must be logged in to access this resource'); - return next(); -}); diff --git a/src/lib/server/api/services/iam.service.ts b/src/lib/server/api/services/iam.service.ts index 1669d21..f39ba52 100644 --- a/src/lib/server/api/services/iam.service.ts +++ b/src/lib/server/api/services/iam.service.ts @@ -33,7 +33,7 @@ export class IamService { @inject(TokensService) private tokensService: TokensService, @inject(MailerService) private mailerService: MailerService, @inject(LuciaProvider) private lucia: LuciaProvider - ) {} + ) { } async registerEmail(data: RegisterEmailDto) { const existingUser = await this.usersRepository.findOneByEmail(data.email); @@ -48,16 +48,10 @@ export class IamService { async signinEmail(data: SignInEmailDto) { const user = await this.usersRepository.findOneByEmail(data.email); - - if (!user) { - throw BadRequest('Bad credentials'); - } + if (!user) throw BadRequest('Bad credentials'); const isValidToken = await this.tokensService.validateToken(user.id, data.token); - - if (!isValidToken) { - throw BadRequest('Bad credentials'); - } + if (!isValidToken) throw BadRequest('Bad credentials'); // if this is a new unverified user, send a welcome email and update the user if (!user.verified) { @@ -73,17 +67,11 @@ export class IamService { async verifyEmail(userId: string, token: string) { const user = await this.usersRepository.findOneById(userId); - - if (!user) { - throw BadRequest('User not found'); - } + if (!user) throw BadRequest('User not found'); const validToken = await this.tokensService.validateToken(user.id, token); - - if (!validToken) { - throw BadRequest('Invalid token'); - } - + if (!validToken) throw BadRequest('Invalid token'); + await this.usersRepository.update(user.id, { email: validToken.email }); } diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index d5cbefa..0a61c61 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -3,16 +3,14 @@ import Menu from 'lucide-svelte/icons/menu'; import Package2 from 'lucide-svelte/icons/package-2'; import Search from 'lucide-svelte/icons/search'; - import { Button } from '$lib/components/ui/button/index.js'; - import * as Card from '$lib/components/ui/card/index.js'; - import { Checkbox } from '$lib/components/ui/checkbox/index.js'; import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js'; import { Input } from '$lib/components/ui/input/index.js'; import * as Sheet from '$lib/components/ui/sheet/index.js'; import { cn } from '$lib/utils/ui'; import HouseIcon from 'lucide-svelte/icons/house'; import { page } from '$app/stores'; + import { enhance } from '$app/forms'; let { children } = $props(); @@ -98,7 +96,11 @@ Settings - Logout + +
+ +
diff --git a/src/routes/(app)/+page.server.ts b/src/routes/(app)/+page.server.ts index b120520..833ff14 100644 --- a/src/routes/(app)/+page.server.ts +++ b/src/routes/(app)/+page.server.ts @@ -2,3 +2,11 @@ export const load = async ({ locals }) => { const user = await locals.getAuthedUser(); return { user: user }; }; + + +export const actions = { + logout: async ({ locals }) => { + console.log("Logging out") + await locals.api.iam.logout.$post() + } +} \ No newline at end of file diff --git a/src/routes/(app)/settings/account/+page.server.ts b/src/routes/(app)/settings/account/+page.server.ts index c8babca..4f81678 100644 --- a/src/routes/(app)/settings/account/+page.server.ts +++ b/src/routes/(app)/settings/account/+page.server.ts @@ -3,8 +3,8 @@ import { verifyEmailDto } from "$lib/dtos/verify-email.dto.js"; import { fail, setError, superValidate } from "sveltekit-superforms"; import { zod } from "sveltekit-superforms/adapters"; -export let load = async ({ locals }) => { - const authedUser = await locals.getAuthedUserOrThrow(); +export let load = async (event) => { + const authedUser = await event.locals.getAuthedUserOrThrow() return { authedUser,