From 2652d4fef603625ddf3af476877a7407fbbf73cb Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Thu, 8 Aug 2024 12:38:17 -0700 Subject: [PATCH] Adding controllers slowly. --- src/hooks.server.ts | 4 +- src/lib/dtos/signup-username-email.dto.ts | 18 ++++ .../api/controllers/signup.controller.ts | 97 +++++++++++++++++++ .../server/api/controllers/user.controller.ts | 10 +- src/lib/server/api/index.ts | 10 +- .../tables/federatedIdentity.table.ts | 2 +- .../server/api/middleware/auth.middleware.ts | 1 + src/routes/(auth)/sign-up/+page.server.ts | 31 +++--- 8 files changed, 153 insertions(+), 20 deletions(-) create mode 100644 src/lib/dtos/signup-username-email.dto.ts diff --git a/src/hooks.server.ts b/src/hooks.server.ts index c9e32e9..a8d1a2c 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -29,12 +29,12 @@ const apiClient: Handle = async ({ event, resolve }) => { /* ----------------------------- Auth functions ----------------------------- */ async function getAuthedUser() { - const { data } = await api.user.me.$get().then(parseApiResponse) + const { data } = await api.user.$get().then(parseApiResponse) return data && data.user; } async function getAuthedUserOrThrow() { - const { data } = await api.user.me.$get().then(parseApiResponse); + const { data } = await api.user.$get().then(parseApiResponse); if (!data || !data.user) throw redirect(StatusCodes.TEMPORARY_REDIRECT, '/'); return data?.user; } diff --git a/src/lib/dtos/signup-username-email.dto.ts b/src/lib/dtos/signup-username-email.dto.ts new file mode 100644 index 0000000..1cd0b87 --- /dev/null +++ b/src/lib/dtos/signup-username-email.dto.ts @@ -0,0 +1,18 @@ +import { z } from "zod"; +import { refinePasswords } from "$lib/validations/account"; + +export const signupUsernameEmailDto = z.object({ + firstName: z.string().trim().optional(), + lastName: z.string().trim().optional(), + email: z.string().trim().max(64, {message: 'Email must be less than 64 characters'}).optional(), + username: z + .string() + .trim() + .min(3, {message: 'Must be at least 3 characters'}) + .max(50, {message: 'Must be less than 50 characters'}), + password: z.string({required_error: 'Password is required'}).trim(), + confirm_password: z.string({required_error: 'Confirm Password is required'}).trim() + }) + .superRefine(({ confirm_password, password }, ctx) => { + refinePasswords(confirm_password, password, ctx); + }); \ No newline at end of file diff --git a/src/lib/server/api/controllers/signup.controller.ts b/src/lib/server/api/controllers/signup.controller.ts index e69de29..ca795af 100644 --- a/src/lib/server/api/controllers/signup.controller.ts +++ b/src/lib/server/api/controllers/signup.controller.ts @@ -0,0 +1,97 @@ +import 'reflect-metadata'; +import { Hono } from 'hono'; +import { injectable } from 'tsyringe'; +import { zValidator } from '@hono/zod-validator'; +import type { HonoTypes } from '../types'; +import type { Controller } from '../interfaces/controller.interface'; +import { signupUsernameEmailDto } from "$lib/dtos/signup-username-email.dto"; +import {limiter} from "$lib/server/api/middleware/rate-limiter.middleware"; + +@injectable() +export class SignupController implements Controller { + controller = new Hono(); + + constructor( + ) { } + + routes() { + return this.controller + .post('/', zValidator('json', signupUsernameEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { + const { firstName, lastName, email, username, password } = await c.req.valid('json'); + +// const existing_user = await db.query.usersTable.findFirst({ +// where: eq(usersTable.username, form.data.username), +// }); +// +// if (existing_user) { +// return setError(form, 'username', 'You cannot create an account with that username'); +// } +// +// console.log('Creating user'); +// +// const hashedPassword = await new Argon2id().hash(form.data.password); +// +// const user = await db +// .insert(usersTable) +// .values({ +// username: form.data.username, +// hashed_password: hashedPassword, +// email: form.data.email, +// first_name: form.data.firstName ?? '', +// last_name: form.data.lastName ?? '', +// verified: false, +// receive_email: false, +// theme: 'system', +// }) +// .returning(); +// console.log('signup user', user); +// +// if (!user || user.length === 0) { +// return fail(400, { +// form, +// message: `Could not create your account. Please try again. If the problem persists, please contact support. Error ID: ${cuid2()}`, +// }); +// } +// +// await add_user_to_role(user[0].id, 'user', true); +// await db.insert(collections).values({ +// user_id: user[0].id, +// }); +// await db.insert(wishlists).values({ +// user_id: user[0].id, +// }); +// +// try { +// session = await lucia.createSession(user[0].id, { +// ip_country: event.locals.ip, +// ip_address: event.locals.country, +// twoFactorAuthEnabled: false, +// isTwoFactorAuthenticated: false, +// }); +// sessionCookie = lucia.createSessionCookie(session.id); +// } catch (e: any) { +// if (e.message.toUpperCase() === `DUPLICATE_KEY_ID`) { +// // key already exists +// console.error('Lucia Error: ', e); +// } +// console.log(e); +// const message = { +// type: 'error', +// message: 'Unable to create your account. Please try again.', +// }; +// form.data.password = ''; +// form.data.confirm_password = ''; +// error(500, message); +// } +// +// event.cookies.set(sessionCookie.name, sessionCookie.value, { +// path: '.', +// ...sessionCookie.attributes, +// }); +// +// redirect(302, '/'); + + return c.json({ message: 'Hello, world!' }); + }); + } +} \ No newline at end of file diff --git a/src/lib/server/api/controllers/user.controller.ts b/src/lib/server/api/controllers/user.controller.ts index 4ac575b..45de16c 100644 --- a/src/lib/server/api/controllers/user.controller.ts +++ b/src/lib/server/api/controllers/user.controller.ts @@ -14,11 +14,17 @@ export class UserController implements Controller { routes() { return this.controller - .get('/me', requireAuth, async (c) => { + .get('/', async (c) => { const user = c.var.user; return c.json({ user }); }) - .get('/user', requireAuth, async (c) => { + .get('/:id', requireAuth, async (c) => { + const id = c.req.param('id'); + const user = c.var.user; + return c.json({ user }); + }) + .get('/username/:userName', requireAuth, async (c) => { + const userName = c.req.param('userName'); const user = c.var.user; return c.json({ user }); }); diff --git a/src/lib/server/api/index.ts b/src/lib/server/api/index.ts index 2a9ed1b..24fd321 100644 --- a/src/lib/server/api/index.ts +++ b/src/lib/server/api/index.ts @@ -8,6 +8,8 @@ import { config } from './common/config'; import { container } from 'tsyringe'; import { IamController } from './controllers/iam.controller'; import { LoginController } from './controllers/login.controller'; +import {UserController} from "$lib/server/api/controllers/user.controller"; +import {SignupController} from "$lib/server/api/controllers/signup.controller"; /* ----------------------------------- Api ---------------------------------- */ const app = new Hono().basePath('/api'); @@ -34,9 +36,11 @@ app.use( /* --------------------------------- Routes --------------------------------- */ const routes = app - .route('/user', container.resolve(IamController).routes()) - .route('/login', container.resolve(LoginController).routes()) - .get('/', (c) => c.json({ message: 'Server is healthy' })); + .route('/me', container.resolve(IamController).routes()) + .route('/user', container.resolve(UserController).routes()) + .route('/login', container.resolve(LoginController).routes()) + .route('/signup', container.resolve(SignupController).routes()) + .get('/', (c) => c.json({ message: 'Server is healthy' })); /* -------------------------------------------------------------------------- */ /* Exports */ diff --git a/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts b/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts index 63d9313..ea23de2 100644 --- a/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts +++ b/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts @@ -8,7 +8,7 @@ export const federatedIdentityTable = pgTable('federated_identity', { user_id: uuid('user_id') .notNull() .references(() => usersTable.id, { onDelete: 'cascade' }), - idenitity_provider: text('idenitity_provider').notNull(), + identity_provider: text('identity_provider').notNull(), federated_user_id: text('federated_user_id').notNull(), federated_username: text('federated_username').notNull(), ...timestamps diff --git a/src/lib/server/api/middleware/auth.middleware.ts b/src/lib/server/api/middleware/auth.middleware.ts index 9c21ea8..b1eb3d6 100644 --- a/src/lib/server/api/middleware/auth.middleware.ts +++ b/src/lib/server/api/middleware/auth.middleware.ts @@ -45,6 +45,7 @@ export const requireAuth: MiddlewareHandler<{ }; }> = createMiddleware(async (c, next) => { const user = c.var.user; + const session = c.var.session; if (!user) throw Unauthorized('You must be logged in to access this resource'); return next(); }); diff --git a/src/routes/(auth)/sign-up/+page.server.ts b/src/routes/(auth)/sign-up/+page.server.ts index 73b05d0..b9db366 100644 --- a/src/routes/(auth)/sign-up/+page.server.ts +++ b/src/routes/(auth)/sign-up/+page.server.ts @@ -33,22 +33,29 @@ export const load: PageServerLoad = async (event) => { const { locals, cookies } = 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; throw redirect('/', message, event); - } else if (userNotFullyAuthenticated(user, session)) { - try { - await lucia.invalidateSession(locals.session!.id!); - } catch (error) { - console.log('Session already invalidated'); - } - const sessionCookie = lucia.createBlankSessionCookie(); - cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }); } + // 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)) { + // try { + // await lucia.invalidateSession(locals.session!.id!); + // } catch (error) { + // console.log('Session already invalidated'); + // } + // const sessionCookie = lucia.createBlankSessionCookie(); + // cookies.set(sessionCookie.name, sessionCookie.value, { + // path: '.', + // ...sessionCookie.attributes, + // }); + // } + return { form: await superValidate(zod(signUpSchema), { defaults: signUpDefaults,