Skip to content

Commit 09d099a

Browse files
committed
feature(validation) refactor login validation
1 parent 1f24b0c commit 09d099a

26 files changed

+118
-134
lines changed

pages/login.vue

+33-55
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,27 @@ definePageMeta({
1313
middleware: 'guest'
1414
})
1515
16-
17-
1816
async function postLoginForm() {
1917
response = await loginWithEmail(usernameOrEmail.value, password.value)
2018
errors.value = response.errors
2119
}
2220
</script>
2321

2422
<template>
25-
<div class="h-screen bg-white dark:bg-black">
26-
<div class="flex items-center justify-center px-4 sm:px-6 lg:px-8">
27-
<div class="max-w-md w-full space-y-8">
23+
<div class="dark:bg-black h-screen">
24+
<div class="flex items-center justify-center px-4 sm:px-6 lg:px-8">
25+
<div class="max-w-md w-full">
26+
<div class="lg:flex mt-10">
27+
<img class="mx-auto h-24 w-auto" src="/img/logo_clear_fsj.png" alt="full stack jack logo" />
28+
<h1 class="py-9 text-center text-5xl font-extrabold text-gray-900 dark:text-gray-400">
29+
Full Stack Jack
30+
</h1>
31+
</div>
2832
<div>
29-
<div class="h-25 w-25">
30-
</div>
31-
<div class="lg:flex mt-10">
32-
<img class="mx-auto h-24 w-auto" src="/img/logo_clear_fsj.png" alt="full stack jack logo" />
33-
<br>
34-
<h1 class="py-9 text-center text-5xl font-extrabold text-gray-900 dark:text-gray-400">
35-
Full Stack Jack</h1>
36-
</div>
37-
38-
<h2 class="mt-6 py-9 text-center text-3xl font-extrabold text-gray-900 dark:text-gray-400">Sign in</h2>
33+
<h2 class="text-center text-3xl font-extrabold mt-5 text-gray-900 dark:text-white">
34+
Sign In
35+
</h2>
3936
</div>
40-
4137
<div v-if="response?.hasErrors && errors"
4238
class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mt-3" role="alert">
4339
<ul class="block sm:inline">
@@ -46,55 +42,37 @@ async function postLoginForm() {
4642
</li>
4743
</ul>
4844
</div>
45+
<form v-on:submit.prevent class="mt-8 space-y-6" action="#" method="POST">
4946

50-
<form v-if="true" v-on:submit.prevent class="mt-8 space-y-6" action="#" method="POST">
51-
<input type="hidden" name="remember" value="true">
5247
<div class="rounded-md shadow-sm -space-y-px mb-1">
5348
<div>
5449
<label for="email-address" class="sr-only">Username or Email</label>
55-
<input v-model="usernameOrEmail" id="email-address" name="email" type="email" autocomplete="email"
56-
required
57-
class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
58-
placeholder="Username or Email">
50+
<input type="email" v-model="usernameOrEmail" id="username" name="username" required
51+
class="dark:bg-slate-500 dark:text-white dark:placeholder-white appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
52+
:class="errors?.has('username') ? ' border-red-500' : ''" placeholder="username" />
5953
</div>
6054
</div>
6155
<div>
6256
<label for="password" class="sr-only">Password</label>
63-
<input v-model="password" id="password" name="password" required type="password"
64-
autocomplete="current-password"
65-
class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
66-
placeholder="Password">
67-
</div>
68-
69-
<div class="flex items-center justify-between">
70-
<div class="flex items-center">
71-
<input id="remember-me" name="remember-me" type="checkbox"
72-
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
73-
<label for="remember-me" class="ml-2 block text-sm text-gray-900 dark:text-white"> Remember me </label>
74-
</div>
75-
76-
<div class="text-sm">
77-
<a href="#" class="font-medium text-indigo-600 hover:text-indigo-500 dark:text-white"> Forgot your
78-
password? </a>
79-
</div>
80-
</div>
81-
82-
<div>
83-
<button @click.prevent="postLoginForm"
84-
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
85-
<span class="absolute left-0 inset-y-0 flex items-center pl-3">
86-
<!-- Heroicon name: solid/lock-closed -->
87-
<svg class="h-5 w-5 text-indigo-500 group-hover:text-indigo-400" xmlns="http://www.w3.org/2000/svg"
88-
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
89-
<path fill-rule="evenodd"
90-
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
91-
clip-rule="evenodd" />
92-
</svg>
93-
</span>
94-
Sign in
95-
</button>
57+
<input v-model="password" id="password" name="password" type="password" autocomplete="current-password"
58+
required
59+
class="dark:bg-slate-500 dark:text-white dark:placeholder-white appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
60+
:class="errors?.has('password') ? ' border-red-500' : ''" placeholder="Password" />
9661
</div>
9762
</form>
63+
<button @click.prevent="postLoginForm"
64+
class="mt-5 group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
65+
<span class="absolute left-0 inset-y-0 flex items-center pl-3">
66+
<!-- Heroicon name: solid/lock-closed -->
67+
<svg class="h-5 w-5 text-indigo-500 group-hover:text-indigo-400" xmlns="http://www.w3.org/2000/svg"
68+
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
69+
<path fill-rule="evenodd"
70+
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
71+
clip-rule="evenodd" />
72+
</svg>
73+
</span>
74+
register
75+
</button>
9876
</div>
9977
</div>
10078
</div>

pages/register.vue

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ const password: Ref<string> = ref('');
88
const username: Ref<string> = ref('');
99
const name: Ref<string> = ref('');
1010
11-
1211
const errors: Ref<Map<string, { message: InputValidation; }> | undefined> = ref(new Map<string, { message: InputValidation }>())
1312
let response: FormValidation
1413

server/App/responses/DefaultErrorsResponse.ts

-7
This file was deleted.

server/api/ask-jack/answer.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
import { readBody, getCookie } from "h3";
22
import { createAnswer } from "~/server/database/repositories/askJackRespository";
3-
import { getUserBySessionToken } from '~/server/services/sessionService'
3+
import { getUserBySessionToken } from '~/server/app/services/sessionService'
4+
import sendDefaultErrorResponse from "~/server/app/errors/responses/DefaultErrorsResponse";
45

56
export default eventHandler(async (event) => {
67
const body = await readBody(event)
78
const data: IAnswerPost = body.data
8-
99
const authToken = getCookie(event, 'auth_token') ?? null
1010

1111
if(authToken == null) {
12-
throw Error('unauthorized')
12+
return await sendDefaultErrorResponse(event, 'Unauthorized', 403, 'You must be logged in to answer a question')
1313
}
1414

1515
const user = await getUserBySessionToken(authToken)
1616

17-
if(!user) {
18-
throw Error('unauthorized')
17+
if (!user) {
18+
return await sendDefaultErrorResponse(event, 'Unauthorized', 403, 'You must be logged in to answer a question')
1919
}
2020

21-
console.log('************ create answer *******************')
22-
2321
return await createAnswer(data, user.id)
2422
})

server/api/ask-jack/ask.ts

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1-
import { CompatibilityEvent, defineEventHandler, getCookie, readBody } from "h3";
1+
import { getCookie, readBody } from "h3";
22
import { createQuestion } from "~/server/database/repositories/askJackRespository";
3-
import { getUserBySessionToken } from '~/server/services/sessionService'
3+
import { getUserBySessionToken } from '~/server/app/services/sessionService'
4+
import sendDefaultErrorResponse from "~~/server/app/errors/responses/DefaultErrorsResponse";
45

5-
export default defineEventHandler(async (event: CompatibilityEvent) => {
6+
export default eventHandler(async (event) => {
7+
//todo: add validation
68
const body = await readBody(event)
9+
const authToken = getCookie(event, 'auth_token')
710

8-
const authToken = getCookie(event, 'auth_token')
11+
if (!authToken) {
12+
return await sendDefaultErrorResponse(event, 'Unauthorized', 403, 'You must be logged in to ask a question')
13+
}
914

10-
const user = await getUserBySessionToken(authToken)
15+
const user = await getUserBySessionToken(authToken)
16+
17+
if (!user) {
18+
return await sendDefaultErrorResponse(event, 'Unauthorized', 403, 'You must be logged in to ask a question')
19+
}
1120

1221
const data: IQuestionPost = body.data
1322

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,31 @@
11

22
import { defineEventHandler, getCookie, sendError } from "h3";
33
import { findQuestion } from "~/server/database/repositories/askJackRespository";
4-
import { getUserBySessionToken } from '~/server/services/sessionService'
4+
import { getUserBySessionToken } from '~/server/app/services/sessionService'
55
import { deleteQuestion } from "~/server/database/repositories/askJackRespository";
6+
import sendDefaultErrorResponse from "~~/server/app/errors/responses/DefaultErrorsResponse";
67

78
export default defineEventHandler(async (event) => {
89
const body = await readBody(event)
10+
const question = await findQuestion(parseInt(body.questionId))
11+
const authToken = getCookie(event, 'auth_token')
912

10-
const question = await findQuestion(parseInt(body.questionId))
11-
const authToken = getCookie(event, 'auth_token')
12-
const user = await getUserBySessionToken(authToken)
13+
//todo: replace everywere with middleware
14+
if (authToken == null) {
15+
return await sendDefaultErrorResponse(event, 'Unauthorized', 403, 'You must be logged in to answer a question')
16+
}
17+
const user = await getUserBySessionToken(authToken)
18+
19+
if (!user) {
20+
return await sendDefaultErrorResponse(event, 'Unauthorized', 403, 'You must be logged in to answer a question')
21+
}
1322

1423
const isMine = user.id == question.authorId
1524

1625

17-
if(!isMine) {
26+
if (!isMine) {
1827
sendError(event, createError({ statusCode: 403, statusMessage: 'Unauthorized' }))
1928
}
20-
29+
2130
return await deleteQuestion(question.id)
2231
})

server/api/ask-jack/edit-question.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11

22
import { defineEventHandler, getCookie } from "h3";
33
import { findQuestion } from "~/server/database/repositories/askJackRespository";
4-
import { getUserBySessionToken } from '~/server/services/sessionService'
4+
import { getUserBySessionToken } from '~/server/app/services/sessionService'
55
import { editQuestion } from "~/server/database/repositories/askJackRespository";
66

77
export default defineEventHandler(async (event) => {
88
const body = await readBody(event)
99
const data: IQuestionPost = body.data
1010
const questionId = data.id
11-
11+
// todo: add validation
1212
const question = await findQuestion(questionId)
1313

1414
question.description = data.description

server/api/ask-jack/search.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ export default eventHandler(async (event) => {
1313
return {...question, authName: '@' + user.username};
1414
}))
1515

16-
return await questionsWithAuth
16+
return questionsWithAuth
1717
})

server/api/auth/getByAuthToken.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { IUser } from '~/types/IUser';
21
import { getCookie } from 'h3'
3-
import { getUserBySessionToken } from '~~/server/services/sessionService'
2+
import { getUserBySessionToken } from '~~/server/app/services/sessionService'
43

5-
export default defineEventHandler<IUser|null>(async (event) => {
4+
export default defineEventHandler(async (event) => {
65
const authToken = getCookie(event, 'auth_token')
76

87
if(!authToken) {

server/api/auth/login.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { sanitizeUserForFrontend } from '~/server/services/userService';
21
import bcrypt from 'bcrypt'
32
import { getUserByEmail } from '~/server/database/repositories/userRespository';
43
import { sendError, H3Event } from "h3"
5-
import { makeSession } from '~/server/services/sessionService';
64
import { ZodError } from "zod"
7-
import { getMappedError } from '~/server/helpers/errorMapper';
8-
import loginRequest from '~/server/App/formRequests/LoginRequest';
9-
import sendZodErrorResponse from '~/server/App/responses/ZodErrorsResponse';
10-
import sendDefaultErrorResponse from '~/server/App/responses/DefaultErrorsResponse';
5+
import loginRequest from '~~/server/app/formRequests/LoginRequest';
6+
import sendDefaultErrorResponse from '~~/server/app/errors/responses/DefaultErrorsResponse';
7+
import { getMappedError } from '~~/server/app/errors/errorMapper';
8+
import { makeSession } from '~~/server/app/services/sessionService';
9+
import { sanitizeUserForFrontend } from '~~/server/app/services/userService';
10+
import sendZodErrorResponse from '~~/server/app/errors/responses/ZodErrorsResponse';
1111

1212
const standardAuthError = getMappedError('Authentication', 'Invalid Credentials')
1313

@@ -39,6 +39,6 @@ export default eventHandler(async (event: H3Event) => {
3939
return await sendZodErrorResponse(event, error.data)
4040
}
4141

42-
return await sendDefaultErrorResponse(event, 'Unauthenticated', error)
42+
return await sendDefaultErrorResponse(event, 'Unauthenticated', 401, error)
4343
}
4444
})

server/api/auth/logout.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {CompatibilityEvent, setCookie} from "h3";
1+
import { deleteCookie } from "h3";
22

3-
export default async (event: CompatibilityEvent) => {
4-
setCookie(event, 'auth_token', null)
3+
export default eventHandler((event) => {
4+
deleteCookie(event, 'auth_token')
55
return 'successfully logged out'
6-
}
6+
})

server/api/auth/register.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { H3Event, sendError } from 'h3'
22
import bcrypt from 'bcrypt'
33
import { IUser } from '~/types/IUser';
44
import { createUser } from '~/server/database/repositories/userRespository'
5-
import { makeSession } from '~/server/services/sessionService';
65
import { ZodError } from "zod"
7-
import { validateUser } from '~/server/services/userService';
8-
import sendZodErrorResponse from '~/server/App/responses/ZodErrorsResponse';
9-
import sendDefaultErrorResponse from '~/server/App/responses/DefaultErrorsResponse';
10-
import registerRequest from '~/server/App/formRequests/RegisterRequest';
6+
import sendDefaultErrorResponse from '~~/server/app/errors/responses/DefaultErrorsResponse';
7+
import registerRequest from '~/server/app/formRequests/RegisterRequest';
8+
import { validateUser } from '~/server/app/services/userService';
9+
import { makeSession } from '~~/server/app/services/sessionService';
10+
import sendZodErrorResponse from '~~/server/app/errors/responses/ZodErrorsResponse';
1111

1212
export default eventHandler(async (event: H3Event) => {
1313
try {
@@ -38,6 +38,6 @@ export default eventHandler(async (event: H3Event) => {
3838
return await sendZodErrorResponse(event, error.data)
3939
}
4040

41-
return await sendDefaultErrorResponse(event, 'oops', error)
41+
return await sendDefaultErrorResponse(event, 'oops', 500, error)
4242
}
4343
})
+11-13
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
import Stripe from 'stripe';
2-
import { CompatibilityEvent, defineEventHandler, readBody } from "h3";
2+
import { readBody } from "h3";
33

44
const config = useRuntimeConfig()
55
const stripe = new Stripe(config.private.stripeSecretKey, null);
66

7-
export default defineEventHandler(async (event: CompatibilityEvent) => {
8-
const body = await readBody(event)
9-
const session_id = body.session_id
10-
11-
const returnUrl = config.public.appDomain
7+
export default eventHandler(async (event) => {
8+
const body = await readBody(event)
9+
const session_id = body.session_id
10+
const returnUrl = config.public.appDomain
11+
const checkoutSession = await stripe.checkout.sessions.retrieve(session_id);
12+
const portalSession = await stripe.billingPortal.sessions.create({
13+
customer: checkoutSession.customer as string,
14+
return_url: returnUrl,
15+
});
1216

13-
const checkoutSession = await stripe.checkout.sessions.retrieve(session_id);
14-
const portalSession = await stripe.billingPortal.sessions.create({
15-
customer: checkoutSession.customer as string,
16-
return_url: returnUrl,
17-
});
18-
19-
await sendRedirect(event, portalSession.url)
17+
await sendRedirect(event, portalSession.url)
2018
})

server/api/stripe/webhooks.post.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Stripe from 'stripe';
2-
import { handleSubscriptionChange } from '~~/server/services/stripeService';
2+
import sendDefaultErrorResponse from '~~/server/app/errors/responses/DefaultErrorsResponse';
3+
import { handleSubscriptionChange } from '~~/server/app/services/stripeService';
34

45
export default defineEventHandler(async (event) => {
56

@@ -9,14 +10,10 @@ export default defineEventHandler(async (event) => {
910

1011
const isSubscriptionEvent = stripeEvent.type.startsWith('customer.subscription')
1112

12-
if(isSubscriptionEvent){
13+
if (isSubscriptionEvent) {
1314
handleSubscriptionChange(subscription, stripeEvent.created);
1415
return `handled ${stripeEvent.type}.`
1516
}
1617

17-
console.log(`Unhandled event type ${stripeEvent.type}`);
18-
19-
event.res.statusCode = 400
20-
21-
return `could not handle ${stripeEvent.type}. No functionality set.`
18+
return sendDefaultErrorResponse(event, 'oops', 400, `could not handle ${stripeEvent.type}. No functionality set.`)
2219
})

0 commit comments

Comments
 (0)