Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions components/Layouts/LayoutInternal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { AppShell } from '@mantine/core'
import { NotificationsProvider } from '@mantine/notifications'
import { NavbarNested } from '../../Navbar/NavbarNested'
import { User } from '@supabase/supabase-js'

type Props = {
user: User
children?: JSX.Element | JSX.Element[]
}

const LayoutInternal = ({ children }: Props) => {
const LayoutInternal = ({ user, children }: Props) => {
return <NotificationsProvider limit={2}>
<AppShell
aside={<NavbarNested />}
aside={<NavbarNested user={user}/>}
>
{children}
</AppShell>
Expand Down
34 changes: 26 additions & 8 deletions components/LoginSignup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@ export function LoginSignup({ variation }: LoginSignupProps) {

const loginHandler = async () => {
setIsLoading(true)
const data: { username: string, password: string } = {
const loginData: { username: string, password: string } = {
username: email,
password: password
}

const info = validateFormInput(data)
const info = validateFormInput(loginData)
if (info.length > 0 ){
setIsLoading(false)
showNotification({
Expand All @@ -99,9 +99,11 @@ export function LoginSignup({ variation }: LoginSignupProps) {
await fetchJson('/api/users/authenticate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
body: JSON.stringify(loginData),
})

router.push('/dashboard')

} catch (error) {
setIsLoading(false)
if (error instanceof FetchError) {
Expand All @@ -120,13 +122,13 @@ export function LoginSignup({ variation }: LoginSignupProps) {

const signupHandler = async () => {
setIsLoading(true)
const data: { username: string, password: string, firstName: string, lastName: string } = {
const loginData: { username: string, password: string, firstName: string, lastName: string } = {
username: email,
password,
firstName,
lastName
}
const info = validateFormInput(data)
const info = validateFormInput(loginData)
if (info.length > 0 ){
setIsLoading(false)
showNotification({
Expand All @@ -139,12 +141,28 @@ export function LoginSignup({ variation }: LoginSignupProps) {
return
}
try {
await fetchJson('/api/users/signup', {
// Create the user in our DB
const { error } = await fetchJson('/api/users/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
body: JSON.stringify(loginData),
})
router.push('/dashboard')


if (!error) {
router.push('/dashboard')
return
} else {
setIsLoading(false)
showNotification({
title: 'Error',
message: error.message,
autoClose: 3000,
color: 'red',
icon: <X />,
})
return
}
} catch (error) {
setIsLoading(false)
if (error instanceof FetchError) {
Expand Down
16 changes: 8 additions & 8 deletions components/Navbar/NavbarNested/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { UserButton } from '../UserButton'
import { LinksGroup } from '../NavbarLinksGroup'
import { Logo } from './Logo'
import { useRouter } from 'next/router'
import useUser from '../../../lib/useUser'
import { NavBarItems } from '../../../types/components'
import { User } from '@supabase/supabase-js'

const navbarItems: NavBarItems[] = [
{
Expand Down Expand Up @@ -60,10 +60,9 @@ const useStyles = createStyles((theme) => ({
}))


export function NavbarNested() {
export function NavbarNested({ user }: { user: User }) {
const { classes } = useStyles()
const router = useRouter()
const { user } = useUser()

const links = navbarItems.map((item) => <LinksGroup key={item.label} label={item.label} link={item.link} active={router.pathname} />)
return (
Expand All @@ -79,14 +78,15 @@ export function NavbarNested() {
</Navbar.Section>

<Navbar.Section className={classes.footer}>
{user?.firstName &&
{user?.user_metadata?.first_name &&
<UserButton
image={`https://www.gravatar.com/avatar/${user?.avatar || ''}?d=https%3A%2F%2Fimages.ctfassets.net%2F1wryd5vd9xez%2F5JA4qHKaSk47mqhd0M8s9p%2F99b9a16e0012ada4e2ecf31a4b4fb1fe%2Fsquare_logo.jpeg`}
name={`${user?.firstName || ''} ${user?.lastName || ''}`}
username={user?.username || ''}
image={`https://www.gravatar.com/avatar/${user?.user_metadata?.avatar || ''}?d=https%3A%2F%2Fimages.ctfassets.net%2F1wryd5vd9xez%2F5JA4qHKaSk47mqhd0M8s9p%2F99b9a16e0012ada4e2ecf31a4b4fb1fe%2Fsquare_logo.jpeg`}
name={`${user?.user_metadata?.first_name || ''} ${user?.user_metadata?.last_name || ''}`}
username={user?.email || ''}
/>
}
</Navbar.Section>
</Navbar>
)
}
}

81 changes: 43 additions & 38 deletions pages/api/square/callback.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { Error } from 'square'
import { SquareData } from '../../../types'
import { authorizeUser, getUser, updateUser } from '../../../lib/database'
import { decodeJWT, isString } from '../../../utils/helpers'
import { isString } from '../../../utils/helpers'
import {getOauthClient} from '../../../utils/oauth-client'
import createClient from '../../../utils/supabase/api'
import { encryptToken } from '../../../utils/server-helpers'
import { SCOPES } from '../../../constants'
import createAdminClient from '../../../utils/supabase/admin'


// TODO: Confirm this method handles all potential error cases gracefully
export default async function handler(req: NextApiRequest, res: NextApiResponse<{ status: string } | { error: string } | Error[]>) {
Expand All @@ -13,22 +16,25 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
} else if (req.query['error']) {
// Check to see if the seller clicked the 'deny' button and handle it as a special case.
if (('access_denied' === req.query['error']) && ('user_denied' === req.query['error_description'])) {
const id = await decodeJWT(req)
const user = await getUser(id)
if (user) {
await updateUser({
id,
user: {
...user,
const supabase = createClient(req, res)
const {data: {user}} = await supabase.auth.getUser()
if (!user) {
throw new Error("user not found")
}
const adminSupabase = createAdminClient()
const { error } = await adminSupabase.auth.admin.updateUserById(
user.id,
{ app_metadata: {
squareData: {
userDeniedSquare: true
}
})
}
res.redirect('/settings')
} }
)
return res.redirect('/settings')
}
// Display the error and description for all other errors.
else {
res.status(400).json({ error: `${req.query['error']}: ${req.query['error_description']}` })
return res.status(400).json({ error: `${req.query['error']}: ${req.query['error_description']}` })
}
}
// When the response_type is "code", the seller clicked Allow
Expand Down Expand Up @@ -62,35 +68,34 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
merchantId
} = result

// Prepare the data to be written to the database
const tokens = JSON.stringify({accessToken, refreshToken})
const { iv, encrypted } = encryptToken(tokens)

// NOTE: We will encrypt the access and refresh tokens before storing it.
const squareData: SquareData = {
tokens: JSON.stringify({
accessToken,
refreshToken
}),
tokens: encrypted,
expiresAt,
merchantId
merchantId,
iv,
scopes: SCOPES.join(','),
userDeniedSquare: false
}
// grab the user id from the JWT
const id = await decodeJWT(req)
// update user object to reflect that they have authorized Square
const user = await getUser(id)
if (user?.userDeniedSquare) {
await updateUser({
id,
user: {
...user,
userDeniedSquare: false
}
})
}
// Update the database with the authorized Square data
await authorizeUser({
id,
squareData
})

const supabase = createClient(req, res)
const {data: {user}} = await supabase.auth.getUser()
if (!user) {
throw new Error("user not found")
}
const adminSupabase = createAdminClient()
const { error } = await adminSupabase.auth.admin.updateUserById(
user.id,
{ app_metadata: {
squareData
} }
)
if (error) {
console.log('failed to update user: ', error)
}
res.redirect('/dashboard')
} catch (error) {
// The response from the Obtain Token endpoint did not include an access token. Something went wrong.
Expand Down
19 changes: 11 additions & 8 deletions pages/api/users/authenticate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { NextApiUserRequest } from '../../../types'
import { setCookie } from '../../../utils/cookies'
import { createJWT, errorResponse, isPasswordCorrect } from '../../../utils/server-helpers'
import { getUserByUsername } from '../../../lib/database'
import createClient from '../../../utils/supabase/api'


export default function handler(req: NextApiUserRequest, res: NextApiResponse) {
Expand All @@ -16,15 +17,17 @@ export default function handler(req: NextApiUserRequest, res: NextApiResponse) {
async function authenticate() {
try {
const { username, password } = req.body
const user = await getUserByUsername(username)
if (!isPasswordCorrect(user?.password || '', user?.salt || '', password)) {
return res.status(404).json({ message: 'username or password is incorrect' })
}
const supabase = createClient(req,res)

const token = await createJWT({ sub: user?.id })
// return basic user details and token
setCookie(res, 'token', token)
return res.status(200).json({})
const { error } = await supabase.auth.signInWithPassword({
email: username,
password: password,
})
if (error) {
return res.status(error?.status || 500).json({message: error.code})
} else {
return res.status(200).json({})
}
} catch (e) {
return errorResponse(res, e)
}
Expand Down
7 changes: 6 additions & 1 deletion pages/api/users/logout.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextApiResponse } from 'next'
import { NextApiUserRequest } from '../../../types'
import createClient from '../../../utils/supabase/api'

export default function handler(req: NextApiUserRequest, res: NextApiResponse) {
switch (req.method) {
Expand All @@ -10,7 +11,11 @@ export default function handler(req: NextApiUserRequest, res: NextApiResponse) {
}

async function logout() {
res.setHeader('Set-Cookie', 'token=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT')
const supabase = createClient(req, res)
const { error } = await supabase.auth.signOut()
if (error) {
return res.status(500).json({ error: error.message })
}
res.redirect('/')
}
}
48 changes: 21 additions & 27 deletions pages/api/users/signup.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { setCookie } from '../../../utils/cookies'
import { getUserByUsername, createUser } from '../../../lib/database'
import { createJWT, errorResponse, hashPassword } from '../../../utils/server-helpers'
import createClient from '../../../utils/supabase/api'
import Crypto from 'crypto'
import { errorResponse } from '../../../utils/server-helpers'

export default function handler(req: NextApiRequest, res: NextApiResponse) {
switch (req.method) {
Expand All @@ -14,32 +13,27 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {

async function signup() {
const { username, password, firstName, lastName } = req.body
const supabase = await createClient(req, res)
try {
let user = await getUserByUsername(username)
if (user?.username) {
return res.status(409).json({ message: 'User already exists' })
}
const { hash, salt } = await hashPassword(password)
const newUser = await createUser({
username,
password: hash,
firstName,
lastName,
salt,
avatar: Crypto.createHash('md5').update(username).digest('hex')
})

// create a jwt token that is valid for 7 days
const token = await createJWT({ sub: newUser.id })
const { data, error } = await supabase.auth.signUp({
email: username,
password: password,
options: {
data: {
first_name: firstName,
last_name: lastName,
avatar: Crypto.createHash('md5').update(username).digest('hex')
}
}
})
// return basic user details and token

setCookie(res, 'token', token)
// return basic user details and token
return res.status(200).json({
id: newUser.id,
username: newUser.username,
token
})
if (!error) {
return res.status(200).json({
data,
})
} else {
return res.status(error?.status || 400).json({ message: error.code })
}
} catch (e) {
console.error(e)
return errorResponse(res, e)
Expand Down
Loading