From 1f2ef70c0c8dcec1ad2834e7d4c8be144b69a457 Mon Sep 17 00:00:00 2001 From: Neil Chowdhary Date: Tue, 18 Jun 2024 19:20:45 +0530 Subject: [PATCH] attempting local auth within prisma --- app/admin.tsx | 64 ++++ app/api/auth/check.ts | 10 + app/api/auth/login.ts | 30 ++ app/api/auth/signup.ts | 26 ++ app/auth.ts | 46 +++ app/context/AuthContext.tsx | 46 +++ app/layout.tsx | 15 +- app/login.tsx | 42 +++ app/metadata.ts | 7 + app/middleware.ts | 22 ++ app/page.tsx | 16 + app/prisma.ts | 5 + bun.lockb | Bin 168032 -> 179655 bytes package-lock.json | 334 +++++++++++++++++- package.json | 3 +- .../migration.sql | 14 + prisma/schema.prisma | 8 + scripts/createAdmin.ts | 28 ++ 18 files changed, 707 insertions(+), 9 deletions(-) create mode 100644 app/admin.tsx create mode 100644 app/api/auth/check.ts create mode 100644 app/api/auth/login.ts create mode 100644 app/api/auth/signup.ts create mode 100644 app/auth.ts create mode 100644 app/context/AuthContext.tsx create mode 100644 app/login.tsx create mode 100644 app/metadata.ts create mode 100644 app/middleware.ts create mode 100644 app/prisma.ts create mode 100644 prisma/migrations/20240618125202_add_username_to_user/migration.sql create mode 100644 scripts/createAdmin.ts diff --git a/app/admin.tsx b/app/admin.tsx new file mode 100644 index 0000000..8d1a1de --- /dev/null +++ b/app/admin.tsx @@ -0,0 +1,64 @@ +import { useState, useEffect } from 'react'; +import axios from 'axios'; +import { requireAuthentication } from './middleware'; + +export async function getServerSideProps(context) { + await requireAuthentication(context.req, context.res, () => {}); + + return { props: {} }; +} + +export default function AdminPage() { + const [users, setUsers] = useState([]); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + + useEffect(() => { + async function fetchUsers() { + const response = await axios.get('/api/users'); + setUsers(response.data); + } + fetchUsers(); + }, []); + + const handleCreateUser = async (e: React.FormEvent) => { + e.preventDefault(); + try { + await axios.post('/api/auth/signup', { username, password }); + setUsername(''); + setPassword(''); + setError(''); + } catch (error) { + setError('User already exists'); + } + }; + + return ( +
+

Admin Page

+
+ setUsername(e.target.value)} + /> + setPassword(e.target.value)} + /> + +
+ {error &&

{error}

} +

Existing Users

+ +
+ ); +} diff --git a/app/api/auth/check.ts b/app/api/auth/check.ts new file mode 100644 index 0000000..5e84fd4 --- /dev/null +++ b/app/api/auth/check.ts @@ -0,0 +1,10 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import { getLoginSession } from '../../auth'; + +export default async function handle(req: NextApiRequest, res: NextApiResponse) { + const user = await getLoginSession(req); + if (!user) { + return res.status(401).json({ loggedIn: false }); + } + return res.status(200).json({ loggedIn: true }); +} diff --git a/app/api/auth/login.ts b/app/api/auth/login.ts new file mode 100644 index 0000000..c6777ce --- /dev/null +++ b/app/api/auth/login.ts @@ -0,0 +1,30 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import bcrypt from 'bcrypt'; +import { prisma } from '../../prisma'; +import { setLoginSession } from '../../auth'; + +export default async function handle(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== 'POST') { + return res.status(405).end(); + } + + const { username, password } = req.body; + + const user = await prisma.user.findUnique({ + where: { username }, + }); + + if (!user) { + return res.status(401).json({ error: 'Invalid username or password' }); + } + + const isValid = await bcrypt.compare(password, user.password); + + if (!isValid) { + return res.status(401).json({ error: 'Invalid username or password' }); + } + + await setLoginSession(res, user); + + res.status(200).json({ message: 'Login successful' }); +} diff --git a/app/api/auth/signup.ts b/app/api/auth/signup.ts new file mode 100644 index 0000000..6e5e21e --- /dev/null +++ b/app/api/auth/signup.ts @@ -0,0 +1,26 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import bcrypt from 'bcrypt'; +import { prisma } from '../../prisma'; + +export default async function handle(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== 'POST') { + return res.status(405).end(); + } + + const { username, password } = req.body; + + const hashedPassword = await bcrypt.hash(password, 10); + + try { + const user = await prisma.user.create({ + data: { + username, + password: hashedPassword, + role: 'admin', + }, + }); + res.status(200).json({ message: 'User created successfully' }); + } catch (error) { + res.status(500).json({ error: 'User already exists' }); + } +} diff --git a/app/auth.ts b/app/auth.ts new file mode 100644 index 0000000..f724d5e --- /dev/null +++ b/app/auth.ts @@ -0,0 +1,46 @@ +import { serialize, parse } from 'cookie'; +import { NextApiResponse } from 'next'; +import { v4 as uuidv4 } from 'uuid'; +import { prisma } from './prisma'; + +const TOKEN_NAME = 'token'; + +export async function setLoginSession(res: NextApiResponse, user: any) { + const token = uuidv4(); + + await prisma.session.create({ + data: { + token, + userId: user.id, + expiresAt: new Date(Date.now() + 60 * 60 * 24 * 7 * 1000), // 1 week + }, + }); + + const cookie = serialize(TOKEN_NAME, token, { + maxAge: 60 * 60 * 24 * 7, // 1 week + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + path: '/', + sameSite: 'lax', + }); + + res.setHeader('Set-Cookie', cookie); +} + +export async function getLoginSession(req: any) { + const cookies = parse(req.headers.cookie || ''); + const token = cookies[TOKEN_NAME]; + + if (!token) return null; + + const session = await prisma.session.findUnique({ + where: { token }, + include: { user: true }, + }); + + if (!session || session.expiresAt < new Date()) { + return null; + } + + return session.user; +} diff --git a/app/context/AuthContext.tsx b/app/context/AuthContext.tsx new file mode 100644 index 0000000..6b2b7e9 --- /dev/null +++ b/app/context/AuthContext.tsx @@ -0,0 +1,46 @@ +"use client"; + +import React, { createContext, useState, useEffect, useContext } from 'react'; + +interface AuthContextType { + isAuthenticated: boolean; + login: () => void; + logout: () => void; +} + +const AuthContext = createContext(undefined); + +export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [isAuthenticated, setIsAuthenticated] = useState(false); + + useEffect(() => { + // Add your authentication logic here + const checkAuth = async () => { + // For example, call an API to check if the user is authenticated + setIsAuthenticated(true); // or false, depending on the result + }; + checkAuth(); + }, []); + + const login = () => { + setIsAuthenticated(true); + }; + + const logout = () => { + setIsAuthenticated(false); + }; + + return ( + + {children} + + ); +}; + +export const useAuth = () => { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +}; diff --git a/app/layout.tsx b/app/layout.tsx index 3b3a953..d964a48 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,15 +1,16 @@ -import "./globals.css"; -import type { Metadata } from "next"; +"use client"; -export const metadata: Metadata = { - title: "Metabase Manager", - description: "Manage your Metabase instance", -}; +import "./globals.css"; +import { AuthProvider } from '@/app/context/AuthContext'; // Import AuthProvider export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - {children} + + + {children} + + ); } diff --git a/app/login.tsx b/app/login.tsx new file mode 100644 index 0000000..14a32cb --- /dev/null +++ b/app/login.tsx @@ -0,0 +1,42 @@ +import { useState } from 'react'; +import { useRouter } from 'next/router'; +import axios from 'axios'; + +export default function LoginPage() { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const router = useRouter(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + try { + await axios.post('/api/auth/login', { username, password }); + router.push('/'); + } catch (error) { + setError('Invalid username or password'); + } + }; + + return ( +
+

Login

+ {error &&

{error}

} +
+ setUsername(e.target.value)} + /> + setPassword(e.target.value)} + /> + +
+
+ ); +} diff --git a/app/metadata.ts b/app/metadata.ts new file mode 100644 index 0000000..a5b08e2 --- /dev/null +++ b/app/metadata.ts @@ -0,0 +1,7 @@ +// app/metadata.ts +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Metabase Manager", + description: "Manage your Metabase instance", +}; diff --git a/app/middleware.ts b/app/middleware.ts new file mode 100644 index 0000000..fa20e6c --- /dev/null +++ b/app/middleware.ts @@ -0,0 +1,22 @@ +import { NextApiRequest, NextApiResponse, NextPageContext } from 'next'; +import { getLoginSession } from './auth'; + +export async function requireAuthentication( + req: NextApiRequest | NextPageContext['req'], + res: NextApiResponse | NextPageContext['res'], + next: () => void +) { + const user = await getLoginSession(req); + + if (!user) { + if (res) { + res.statusCode = 302; + res.setHeader('Location', '/login'); + res.end(); + } + return; + } + + req.user = user; + next(); +} diff --git a/app/page.tsx b/app/page.tsx index 9247219..bfd44ed 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -21,8 +21,24 @@ import { } from "./api"; import React, { Fragment } from "react"; // needed for collapsible +import { useAuth } from '@/app/context/AuthContext'; // Update this line +import { useRouter } from 'next/router'; export default function Home() { + //AUTH LOGIN ROUTING + const { isAuthenticated } = useAuth(); + const router = useRouter(); + + useEffect(() => { + if (!isAuthenticated) { + router.push('/login'); // Redirect to login page if not authenticated + } + }, [isAuthenticated, router]); + + if (!isAuthenticated) { + return

Loading...

; + } + const [sourceServers, setSourceServers] = useState([]); const [proceedLoading, setProceedLoading] = useState(false); const [destinationServers, setDestinationServers] = useState([]); diff --git a/app/prisma.ts b/app/prisma.ts new file mode 100644 index 0000000..63e06ac --- /dev/null +++ b/app/prisma.ts @@ -0,0 +1,5 @@ +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +export { prisma }; diff --git a/bun.lockb b/bun.lockb index 8eeb41fe202fa938982660045e0405b122989639..54d00900d1fe5618c96d405fba87c8d9e11a13ce 100755 GIT binary patch delta 31053 zcmeIbcT`kY)Hgi$${>RxHWZ|TT|ufK$RKtdyHT*Alu=N6QNRM&f{8UpJ;o$|KBF&hpiO-Vj%qS<9ON%ik#178XWGeJ{U<;IA zhzTGT+0?t7PR4*5}CUBCOMq>lq3)mL;A~5k=09yg47*iADh8s1F z(Rj8a7%oUqN7eB~9mPbXN~#S)>!d+NrD4;Ga(+~5sxdW6qxlHaQTe3A)HLukRZy79 z9|51*ONdBFjE&Z49)hk0Ix`|ZjvD?AGzolDfc{kQG+LpGXOsrBpg!@F$e#EN22G~l zg?!R@ol@?1U@G?!_M`?QQzD|^8BIFuP5kY^B%noAIe%zuS}e5}o9ZTcV{g?Mx!_Mq z!;^rip?G7;5Tlnyqw=RieDc}gh}5)XG>~FUPbJy9Ln_j&D(nj5vELx(Q(76 zKTWp49*7PNX4a79=?!;KNT|(6BxJ@%q(#L19y-N=C*HIOnXA|=&mjSR`Uz~q(KAqj~oMvcYw$XGWEhv7Uy7XmSy4)xd2^Kql4oj($b94n%l@G zsf?-VX|ZV3n3|k!Ov%&~*Oj$1rY5Br(fM6SO&&4SlRJ%wN=zYjFDu%l)R+0icC6II zA^RYjrEW&V#6}?&5E;e{w4%AxK<;iuLpd^&BGO{qh8Z*Aqqt$nPiQ3Tj-lcP5r;&9 zrgoY(mdj57rU;A}DhEJ%O03(^RE;JnJs~qHG1^$&O^WF3oKh65g@Vf<2po_z1sI~{ z+;P`vkjxo}FKQ?in7nniiLBpYVDipb4;iNe(-5lcDeKo4<;k1PfvMe8WBhPqibm4` zQBBUtjEIYiO=UGqt2RqQf7EmoFg5XCx~PE#L#L(18dKV`2-BL~RZDyweq@=5gw$BI zRxt#jF|4-kru60lOqSCDlfa$W5z{8wKLp5PS^-n;34e`-rYav`42zuVz)rxWupNcP zV_=efTyu>EgFR5^Pn790*fDlboaG+aj=roe1zm})~o zlOP=x><>)sGz2Ea?b~&g3tZ_Z`>aKOIR@ixQf541O6+5CM8fzm5 z%MF+eks~KQHX$}XHZ^T%YGQ&fBJd6JE25k#P`pyUFEF(eZH!DuRZS(dM}CoM*1JO0 z>>w1e2de?HoPqDh%1!wWl`VD&G+8b!A}Jy=&ZtpmrLX6oPW~DV7OtEy=trWc^U>Yb zvdvBclWn3C(~JqjH5!_PV596D^8{J z(zI8q9Q7-J$!qU{$(x;kNsy5!M;Z=^NFM?}Y24Cf?>hq%e+MwNr$#{y&{V&If(2l5 zc0^K2Vq|s%BuON_5EhYytX4NUdVjg#x`0JZ^bf^wwkhVgQ{ak1nmji%8A z*@chC$n~F3PzKH&WRNE6CCS%U9+!|$9iLJf*a=JmAzmbnlTu<+lEw4W}$JDrB>}6$(9X-AuN=_tD)jE&bpqAGg0_S~M$K7Vz`n0WFK!!^eE) zu-9>7s_FF6QF8(^etqJs-z!#tPKmPySwT3(|015rEdJ*`Erx+dg0F5cRQ?f?>2n>Xs3$d zzdGG%+}m;Sk>SI{vniPuEygZjKSrJY5GSlla%uhNszlF?aZjS=J&Nwz>uU2ot#|c^ z-6wuLGks~H5cQkss*wgr7Wq^G%^X!)~Z%5^r1aE4`+S_DoIAvokR4(_^uG}UbukH_D zIF~s);3t^$eP&&iw8xPWv3TvvTb(3LTC0CtY6Eq0DHZ7 zryR?&*9&%Rn|-j*m%X(|jtL8JK#mE^a?lF_Y@0){I9AI_!CS9o0glMhvMfiv(1dMs z4AxCG)o7Z-TN+m6;HNtZ3X@fiz;YY-vrMO8UEA^+4aNjGMRtDLF`)EpXPrRp4x|Fu z2m3(XE2KI~EeLjg;v_Rx=&To?nz2%6y{;uD4azIW1hhUGR9m@qM?dkZjupD-wFb-w zgQ-cez?=oR>cz+AEX!3dc(HA+!Mb=1?v_#&G$5|6zyfOPwbc=>tyz#`pe_)`ZFmjag>hV4*+T zRySBT8^f3^BIRhWgKEh>#56ayt?tjOL^_Y71wsb+|+ zpRNT4h#piqMzW0oMY2hhxVJJZt*_UW$4n6*RnJBB5Kyu*A&!2!Y)~XddC4(HKv8wA zMAfJ&*F#<%KcOGXY#6MY3?3E@65G{J_XjAdElP$r!+0f8u=G3nY1@JdV598=#Rb(^ zVI#fntdgx|xsHB9Ro1L=un^5M8wcw+W)f-?@!;eq9O0D|OTBoG{Um5U3r8XmStMEdqz431Zp#>HbuxaxBE&Pg@m}Mr*dS zZlG=mQWP~JBRl>}I$Z(ss0BonyPxhpD0fg=NwJ!kh(lPAZJ;g;DT*}srIDX*9;iT2 zRi*Zxfb#xKIb+V0l_~P^Hv>WbfDP;Ti5qQLsi$7|6ii>Ktz3+N+O=R2HX5bxP@ZLa2kSEIWou|8DK;vU6sx*Yg@PXAU@4zq-C8Ut zWIGKLFjgKquu>nruAZYDZWydkD-;y==cFVC-h4+^=&KjcIkHk;z1GnQ24Ei?0tF+> zY#OZ12c5!pIt1$KV5dVag^qRobp1g2%WXhx21*H1S3m6qP#EmKfjTP}IWi%hgP*no zC=7jvKyjQ4%ktNY`(0R}zh3tVd}MqtS%rA~sfnQpO)&fAPae^t} zQ$1E1q}O@Xmjei65n(VAloCN0(VIZY69Q?iZJ>rOC?SY73l7#Lg4bA*07Fmr3n&s9 zZD9;+8fr9NQl2o>-|RC%O+|xJ;v3W!P%TkVo@w5KBIC+)u4f~;5!eJBi~>atE-%I4 z9#B+LcF`M9)Hx`K-U?Dtn=lOmV;U%VBp3PmiN_kV!j^hneK*+wsDNnf4@!w7RG$fo zG?yY(`!}fmG_z$9SeCb5mj?z7NKq2_E~qY` zWMzEeUCM)^=wqrvN#biyfNCj4xQ(am11X+_o-DI%u2`yX!%Oh>(|rI%=E79x zNB1Qgz)#2Al zkmqw{T}OqIr}T-S6mz5Gzw>q>kBl!cp#fU)lLbXR2-*o02`U*)oa)C4JL$zg{8(ux zy*3FAhO-Zy0(Fm(LP3a~>+dJ7ZpI2b>vcDp$rE7(X=FOVbQGO((DeXC;z)Yx7$~x= z?3v?8Vjh9_D_|D>jyli|L<;j0Qu#>rrj+t`wj6zK<(Vsb7FVTC>Qy0>7G@8roj2u3E~X{7~t2`CB{w>JSeqjDlNt04eA# zhaoM3eS@{`7|aH?6Yb_9)mGX{o^T>>anBoj3E)9wK!sjM?a zoROk%wXdI652~YNfIOrycqyqdxMU4dC`2ZLt4ts$2B8o3OGqJ_NKW+oD=1}WgHrcEg-iXm z?W4MO9#TFiZ_0A*{lwFKSYfnYXNmB`AjK9aq@$nEi8V6@>n4IH$3RG+pY||OET}_s zvwmt1Az0{ogOYnkA#rX$mNi(fdj_UFiD9wT_Uw;!KvH2LQdAg9qVVnhENh5fH(-G3 zGK8RRHz=5zv~=>*)(IysVKNqXg|k8jy{^_kd1%T;>;0L+(A50|iWHOc4wj|F(t)fn zR`0_wKC&wsKLbj!K89!mmN_g~_b*H8 zu0*Qlf**vYtXW*JZd#N)40N=?5RXT(!Z=I|(W(__QXdFPmKys8Ed%AxMz;;r9aBnS z%Ly}E8r3NYtEXUK%@Tse-;69PK`%ZsvO=H@gIQ^UUS}~x9vITpu5Aa(p9M7z)Mg{) zMVgBRLs)5|{@=oDA0itYcn}yafh|YhzeIvi=L>i>1o&EMjSO zq-iD*siCl*bbX+E391DsEaA9XXoHOQQb0^W3bPev+T6x|y7Qo#f|6Ieio@hxIWBy# zWJiIbr9h@OgTkDtQ13t?Y-CCs9f!UoA1^@)!%!*p7L)-L{E5-sI$l#|Y2l!Jkyn|< zgzgVeG$0Q(Eh_PeI8GuUj(t@B?N5Bc)J6-d_EtP_i`FK~W#l zxY1Qh`5aW(VYE{yltd)20VPK=ZPHCr)mcc|n}U*OA*@*1t)LJTSl-H~$rgbIkgW|U zEH#iQ8!7Uuyyw{s$_tb{hXth5gh3mrQAo+&&vnB{0!97Hn~w&=<(6@^1xbe}6!tCy z{mm3Y-p1YnMaGBw(1_27vI?exBIC=ZSghp91NQng_;tZjFGZp#?V-ztaV5QvC*TN3)!r6lwNl~TaH19Jj_q@%3)cf^|~QB zs*A|nKZBxO2!h7nPggnjv-92j#DrXyHAb)7047DNw6W2>2Ia4sNNh5e1!U{BQDZSs zB@tF2CCRC)H%@gjtpKAz1xqDvAr&O0oW|3_NrDeWssp8TyO0W&IurERmxH1KAzwfE zO!z#p(c%mW+lU-%3LDX4A`2L+*LImmBcIm9sYp>kAgbW>U7!L$VU~eQ-hgTgN)9Xi z_p+O%MMIkdO4>x}3X!5x2$b&rCV4ak;O2z5JdXv~>a~x+z=p#%uzs&e8Vx+2qt$3M zB0&8#1iumZo89^cM$fscWhJ$$3pF=vrM*8@%E8z{Jul26!_?VM3Fn@V|sA~l)7dY^fvH><1$U)PdcMEm^lohM8 zG?Z-xmA=$~n~p`GVwPI4I?D{WPmsJUlvyvcVCO*DGUw%?>@cVa%MF4(D+ZOZ+=BV8 zFbIw;cSR_xk4GH0K{+$em7(k+s97rwf-AcTDsLt9TV)XHuoXVe6n@ zfkE(Q5e3k%0Q!OQWmVQgKTzrG4T2vl0u{3!`fV@>%~vRFNA)eHi7EOgzeC;5c;8q?yLY* z*mmf*!+`sW5j&vY4(JD}7pt-p`hiN{X%IqL5vZ7*&~KMP=);nCLBCzl4^%(qyc_y~ zny}j-3}D5eGIm41JqBSQ%iRP0_CP;S5zMm)`hl8NWDug*O;CA7&~L8+clBrNg?@XX zAE+TLWFPbcmA}s*#Ih1lbM`^M{RUweTeu(k?T3B`40u}8{Q&d>wFy)r6AnVZ1JLiF zK}co=pu!G9ze5Hgl|>waeutnRsB~84F!TeJe%K(4U`3!}4nw~q1|gFrAAx>HpdYA_ z%=swv12y5O0b9^wP#H&|-!TI=mAS{D-!bS1Dwlcw0sTPD`okcMV>dzN{Q>=s8-xjL z#&PI(9QuL!o`sx%exUMC7=%fz1k{`p(C?%{_>nC<3H?q&zf%Tb3hRCf`hnU6Y8n%Y zq2DR!S8NbwumVtF#nA7xL0~N6H1sf5EiqWpz)FxKt8uSB|e$60kVMU-~u0g-+ z24NdZz7GAaLqAa4nez?k2Wr9%gRqkogUYx8{cakB-7NPe^t%cDZW>t5O(UE2mqFOa zZvF+${(@$=48j36;}$f#1Le5HLbE&2?5;s5W(AB0JuwKk*^DR9>It+0b(e)ag;t>QpBjYwtOV4Yr_kz|L3qd(K7&@z zpjC-Mc+9$&Kr2w2Ks{x`b7)lpt)3f%5>@~z>^Zdh#~}Q}BL0C^|3E8HFIklr&POZe+0`~~VAbAAPXftv8jAbeoOpfX;;U#|_~Ck-3>x;Lx; z8V-AH5CrD=1`2|j^+sCoK;^xGf^Ve-?=2L33k5-yXCb9f5LAAtwBUi7Qwjy&NekXP zDEJNvzLyrf_fQbjCQz14_y7gpL%|Qyf(I(>0~GuyEqEWH;72G3%8FI_1O-8*f07nF zP%)n@X4Mg};LS=FLT6bE7PHQQvgOVK$ispKpCEv?=f$9Th5&}I2!ByV6FLf>ynq;CW+-8$LkVvl zp##HA2gU(n`0^^|U~DBux;YqryoeYv=3vyR07f&OTmcO03SgWgMj&^t2*zPzOsEJ( z5HBW1Mny1uEx-uoxfWp5w*ccdF+#X!B`_`$V-~63ir*wgUL`QvSc1`p&#(l;-x7?M z#AwGuDuZ#482OdK=)g;eF{d&Zy{dp=;0vpO(Y^{8+Nxl5=H07;@rD?ih|!e`)xcO* z6%1oFFuL;sVuV!#!>T$MJ!Qens)KQW7`=EEE0owujC3n7LU|D}VypxszY1-+bq%E4 zY9e*G1`16;N*KV4Yobs_O)z||!5GMMt-+{|5aPFq5y3rez_>_^SvFuq@tefRvjL+` zEijCHMlCS>Yk~2S7(;l7Eg1KRk#7q|EH5F(99uAY*?}>PFSG-ry&V`@dobd8cY842 z5MvWD61m_2#xi@sB3m=4oqs|^(8SE}fgK0>Hp^SF^KSV1s>Aly_xH=(_jbnUx#ja4 zcD{Y-VCV7fNjEAbbnjd7L*oGvFr|URPj>Fif1v;t6BADt2f^I zw818F^}2Vj+N4dLI^^|-_T7u$?HuIX%VzxFmX5wHZQOq;{`HMl%+Z1U?4m!7o3eBD z!H~vNoR%cs;~z-8(C35v3tAff&gyk^NwJ%s&{!+gd?u~Z- z99}_RaOL)%+t0i7-M47eG_x9g=di^y!zM<0?$!^mcjE_uzgfZk3FZEq`Y09Qpsk1%~juP2k9?Nf4R%#$fYs)y$>Jm+$!bS>|Ac)4DrO@<1N}u z^;qd^`>D>p2EC?k2^+h>f5fFH&ui*WS1dl*?ef_vX>Yn0Ss!?Gf7x`q(3agh40XKT zsoSw1vwg?;1P(k@M!b<_#oN;4dguEMmITdrHA(z!x>fqEMK)LaKc2Av#~L^4c&9G^ zqeIw|dXwj$vpKh+b>%J#kD7P78uQGqmsO*MXQo7N2%Wg2fbS&n%$q!@d}*D-NbAK{ zIyxWF`TcaQ;?fTH?faY$?)83N($Tf`zJEKXdB^tkXBQbq_L&^-@8Gii_`v)3`vtxJ zZStx^GhNGwH@d8Nubw`d=d>~^>206OCap&us+PNXP@M-0654c1cQJFX>ReblVDBaE zlx8+Z*0yfg)PbGZw*Q95+gqLebpelefp`;Jhn95iJ$c4}XWPrwi<|yQ zJNv+j237Y6J$qfBeo}*sAz#pC+QT(V-<#tW z_2<}=ZaRUckI9#48;x44qTqUG3II4IhhMe zY*?jMBldOmyE*Ru*1M7Mv$nn)8$L84^6l6G+};)9#b5pL;coXygQ|Hi-!`OIs*iIc z*Nnex1GDS~H*eNM+%QGga_u3_mdl4v4_?})+|Ur~zgWeXs}+vTt^3}-m+Pkj+^ha9 zUT#_IHFU`}?5P(P;ngI-(dzBmM&<*80~Q?%|8Zyeqt_dK8r}V!Y0!vIx)C;YYNywG za6Pw5(8?X()n3zZm&uOROQvRQa`>JfC-L6>l;zpvZMZ?)mbI(&X7}~!_KzMuIX7Zy z{KLGhXFD92usGNB{I>Tx$om-`8{dNBSw#%wIBXaH}zu~@g|fN?_`7D?>^kM zu2I!~P9K(?pEqT|z8Y^@HaOC8c;kyPv)t`0{aSCm{3LCSO|20F%#zohw$V43TI8XN z>+{#v?)L(!g!7i%tq#QVtbg(NfhqnL4ts?ACtG>u=O*s0w0@dfXWLQnSwC!eIMvF0 zTCraD@IuMNE%$!C=F;=HrOEcRtKQ*Nhw0Ash!}sbjCkLd6>o3StLU!zZ(a9(d{FiD z%WKt6zkJupJ?7{Sv)+Hu9H@VNZ`|RHxqY1b+t}N8U*o>njr;!j{NZ`i*Hgp)Dl!|i zce2{J6H}J)7=-Qjmrh}*d5+tSo%BX7xQm5e|dSJq4jIW z{ygE#+$V#^rB#pbcq(G;^EZpX4=N+xk7dQ{ytVYnmg~z8&2#5f!Z*I#vB2V4W5=Ms zgq#!mA2>zNS#<7kLB+i@7T63bSbN$->`^4fu6??_+6x!Y`tz=>`T2~~CLUA|;>{be z^L^g*k9LPAOkJMV?6T>Vca^-)G~DcS>eD|vUi^NCP53FiVC&F>zh!>vefGHTKk>q= z(VxDH&$4NJyz9_p|3zho#+0(+jY|#>j<4Wn*1~OfX$Aeb5xK+XRlIh(`MXU!9$Lm} zLLUTK-D)@ThhsMHTsHrFRrle|Pucxyoz>h7U$(7uLX!W4597Am1zXScEjZM$OTpRSYAt%!|9SA3?>%yxiq#KZ?cvm7|JES0{s+$- z^S-oV!%w4J1{VCy+n%a?@y2tPqGM&uIfM71jayg)?BsSjpdZF{j$oKI1S8%Nj9Gje zF}4!J-U$rOW1YZ=X#~b`V*Jc)oWZbe48~|@Fn-}jh;f)0ZZ2T_#xq^O$Z!MWDlz8s z2Bb}WcQAr#gRziLstv|PVmv0sV&1F{7HcW^7(yY_`OkHQP+=J_ zA+(%#s0XxyFC?^*za_Macdrk$nlC4`h6@dV*781t*6{*D1zgti7Zcja8@L1Q;<oWg_>JBHxrtO;;VXm zOrS8&;$8#!e@&hPSE8DZqFm67d(!pJ9KA4IyTu1je<(9h$*i#@K18T#YGa0{))shJ zr%n`DHT-zwaHC+WSt<)vy^NUJ`2_k(KfJbHM z(c4yH;Il|6N6+ngQ=|AS<{^uPYC%hsEPB+}Psz#$rY6k*{T1Far5ruF9iZ@*^BHI& zdzDhW0SIJJ4BSI)G_|G)Z+(;W;8*6=@Rckiv69`hn8PVTI>GE#vdu zF>by}sLT833*Tjv%Jk5d9^le5>KcHW02@FpfGxldU=MHrI0Bpi&H#G&T?s%B$4vp{ z0cHSt2>%A~7ElU!2Y3%S2si{d0N4Xqq|t0~{9P~#MtTn24bUAxp6?0x4$uie&$^od z=*f6P06i(M59kQV4S-I7x}fRFI{hx-6M!OH0MHL1C}{ow9OuPLvB=C^Caf~j^X)!> zz5sf1PtU*U896;i4+hY4^~L~qKoh_#H2D%hKOdmqB+w5ht^lqAt^q2+vh-t%Gk~)I z^7(n5xg56uS1lK2^42Q^r@Hj;{WTh)UrpQv+ymSPke?m`=vNjuL_TzdFuQ6r%C!gh z0iJ^Ym&hxv6zYn9i`;9a&_l@MxhsW6b%rAwzi-nt0Z{DG?<1}teHwTsz!!44^5T_3 z&4%qo$3-}o@1CRtr z1Plel0tTV(Bfxn;JwP}h6fgkL8_)}YZlq!H9dJ(oxsF^&u51Aa2GAxJKcC9hxFOLL z;0wSStHB~HHS7V5U+~ZmVKj9Ct^gV<&`%lyG&E>j(s-pu%`_jQV=u)4gdrK^Z+tb2S6)8OF#&K ztVR5`fYyLE04mcCK*7`lK<#x0bOlgZvTheZX8;9THvsXoDT5gO0OUvVVHlvVLQ}er z!t1Z_&H$eV90VKykY5J@_5xBeg%+476KLk z<^kpdmH@KJl%zG8m(t`HG8dU@86W{b`AY#S0LuX@0VLEQz*@jMz*fK}z-GV}zz)E6 zz&^lUz#hOZz)rw!KoOvr3~>sHLjdZ05P;~D0HTiq$X65}hXF?b0^kJT4?qh52^s=8 z1~?9&`Xrp%&N-yd0v-bH1MULu0B!zAQFR1Rnzt_y?6NN+=K|m_09E-L zK&2W28UX48?g1VEVj$ZPKm;HZK>ICP(OUqXApbF75CEivn;;Pk&;x=19sqJ~W8lib zjp%5kh9GDnr+q_RKpjABz*D7>m%wiT?*Y#N{{Y?rUICr~D4+Nx02hEW;01un5lwk& z8?TWr1-w=G&nZx;0yX4{PF(;ZfGRix(=N&ZK#kBYi&mRYln0=O)Q(6XT7?8aEkJbu z?Qcv06#=vXA_1x6nn;uIR>0Nx&Gp!o(zj zQmB0$VxHhp*idE2t~Y=}mn;zoOjZg2Gy{-JC?v@Rh@EVW4-yn~{=j~K zrU2S>QNz>`H9~2^)G1BRWjiG;sod8*1Na>Q>P*l9>GpuOfHr{E0B7=lVTN`Ad}Pd(ihMNKvPmLfHUei0cd8W_GpIr4nVHy3FrbC1bz?T z?tpID`07dtKv@k)qu$_62Mz;v0=5Sbtu9by%d1wPseK^IlQ1oi9-!1Efd&C+&I||K zBpNt-FutMy(SS$*H9!T4M{_-4m0xzvFoKW2&-oO{!@!6G&=j8l91oZQehP3hU^-wb zUVRKy}m`16&)J ztos8X66s06d4TT$;{oFU=H!2>JP|MfKm{ptGN3kK8Uc6&Gm)m%jRF4zpe4#1P!B-- zIpA@?EWm6fpX$s7{0jI5&>!XI0WStD0xTp67XZjC3l$(*$^@qLHig~&+Ik$B44ZqtOERm0u+p&B}AG! zS`Sb=EkK$YuLrylpqAZGri{u{>y%ZfOgdY-6R`yqw*ra)djPuuI{@1Og#gm37CJl! zr~$A7ke_#gUkP{@a5Z4cr@DT?RObL-A7DSA{Uv7y9)7?|E<>fXZUck=}_;#VQm@V)-+XXK>3Mw69&og*Qol)be z)IxD@cQ1EOFGL9U*&%eY`%Nj4VHbPqW?uMnlt8|_2X^8D|8<98ZAb2e5;>zx2mBH= zIX^*a!-Gof68I^U5RVA_!4APoJR@+2oq~1Ko2Y@sBFA=l_VqP)H(?14_i^`tkeY|c zfgy6bzTLNJhe_nzFF79t9*4R@MLx1fu;KG|3O;tZutZIi{>5s`gIW`I3`1!TcgT-0 z5&4sy=y9RQU3Lkb#PuRi+J&Ao%Sl&-Ir*7kP+9fn}{S$R7qPPEUqXw_ATj-468VRQaZ=oTdvKz>W7wr~o#Nnp=%5KR}gFZd1%ygIx|Z)UA!E;V~nC+GyE3^DwE=a zIz&})nbhk~X)oePJ%BTR(gN3p zT^8XVjUtiUX+OE60F(7-z#K z?ccxYSDhlxPxjH?s(ju)^p=AVsSNdhjGkGrg(_#?C~B6>~t!~clU z0AT#gD1JY}KBpSrv0t#ZRgdHRzI=muofofPiUu%7++o>p{*(%$zWxB(nOTE}9Y8zl z1U~ryyrv!`+H26GPo>i%UP(Q}u2}5&KL-Tw|6wq|04)!~WS6XY<3nirwl&{>5KSwz zoqCGry=6aGo*mE5W`&jy|QyZeyZ>M4g;*S0Cv+vI;I ziPeb&4H#IEqO{J?=8V)9_ub-0Q1Qmj)^f4Hx$~ ziXnZ_jxQiwY{#Ea4fO!eUw*E=t20~r0yX?Fm7zBX>~c&PY^NR(YIAso?&^efD^Y?* zjW@1E?D&#n&=79F2rQWJ3V*;17Ce}cHBTi}moFtGS*nH!+I8j&-@r-+Zh2g=@l}sg zbsFArT}t(id(gUvdoyZXJ^9k!<)-T%oy#`lz&E5z0v~{?cIpwBeT82yw|zYyu2;uG ziUa@YI0XCAfgdFNivxE*0cF)QTm5Id&2-*Z6$V6Sa3xl0xrAC$%y;0CCj>8B_3*Kx zs(~*DcKYd#fa&qzFSu$_9O zYS-1pBOYI{!4FKOxItU#9=!2c%qQw0tB>A9UL9D?%m*cuaaEZQJS*EF=^_H@r?Wy; z-DA|Wg)utt)n^57+i!UPv(E2O*-kw(HZr+(P>Twd{%9R@6&YnhWA1TI=tSf79D2b} zm~#$tNAqJK?bMT3+w6;qI$rOe6x5?>67` zf$YOOpNG*q@ge7hHagXbGFLp|&rh8f4(e)S+MEP+toZy3m@Os={Okp|^oIc694ZS+ zX+#29KqckM*5dHyd>7cZ>bb0k@B6GuUw7@1C^VFIIk^1bB^M#X0AAq|=2!K&RPUdn zvWk9NfbfjPrGW*y9~;CMd=#vB;w8aeoD#&RUcxw0Ph)+bxAdorCz7p1!Al+{ngv08 z?wCHRV~~ApEiX)!xZw)aQ_sqLc4UoP zbr;jqsOs(ROEYgro{a+S)dNCbubbJfT)*LPE+#-fQbIkv_OEAkuQv2cyo?+_cW>Il zsmI&)PJ5Es=abbwQTVaX2`YVMJwWZ^*aP z4DZh!uR%}sIMzpZTk2dpWGzEOG}}PWQ2xs^l#Ax^*Mvrxqkg_7IEy3O@g3KMhPs(( zlN?it-@J)JHF@_Ep!&S%A0TgD6u15dsV6vn&%jsDb=d@X9c>HBQ*4VsE+ z4Mb>4P1P`g3f?^Vkx<=9+We>|{-hA`)eS!ieI|C+KDGU#~eKSFTqDu>faWT%wG%BUE?Q+qgS1Hms@C9;6rY~ zcvbi>x1fJLKA+s@!A}yU=ht6Ajc;1s(+i_Vv08hcO93J~7C&hp2LIZr=Mj%w+_huG znm=N`h??G+FZdgqWc38&q!y!A?#T|Uq?Bli>uX#P(}HSU_pbA`>2qDbMeQ$vec1=ekG2YGVH*{X*;Ynr_nzNp=g zBfs=04~?eksnJ$bGn_wOI)3d-ft+sgHSB|kHQJ!G-_I#Im|Z4y<5$p!t$MO_xn3iF zxHH?1cJ0bQs>dzwLLK!e>IFZWH+9@L`KeL>V#=axp}C^WXQgDpRr0?SfE1NDSwxPQ z8YWmQB^%bX_tgAP(J$3e!cz%0rJ=8x(yXG)BcDrtW2z}TxU2IfuLUh8A>jeS@_)^d z-Nt`@AlNH~RlU`^pXV{hhtTyK4JtF7G9Huy|3*?9(yXSaAx)LlO{FcmQdF6K%69O1 zE|upi8n=inD-+C<9l6D047C4n?-!@avsw+F{1|%&#nHC^J$Zezci95sU3vCUrwh5L zuX=QN{M;SRGa`2FMS#&B57F_>>vU<-mao&B(pxwjG1A}f@q2sB*gaZ_P0XR{`ya#$ z$vHg4tC5r=T?C9T$KOA}M(~@0{(AxXW|40(MRlFM(vRs7 zja`XQ>)Wy@VIxnfw(3FSd3AM~wewtQ8?8)P-)dc58{}IU>QX8y)BOK1!_zuj@*Ht5 z&%^49{5gbaDYjLQOxK+1w*N!BTelQhFeQ9T7G?GM+<7%_iFH9#)+RNi#N==u{sMEe zdO*Bg+uY&zE{cvy<5>38v)A3{I5&6@;^MC4AXI-2=c``8r_z$+ZTH{v;hJ#n{u2I? zrkNUiz)Qi;)^wnJ$8EvUA(bB&nhZpYBP)mQHP z_iQ3NT^Uj)Um5IMMVqSUyw~{X(q{MCGgBcQF5Nxc{WL$~hBn_lbnwNuHi7)f8=>*%Eu8~*eT!>m^?dq@D^8sXF!_}ha@dwe zq9Uc8daS&8^Pt?d!t~`RfsG?=9hK3f?q2!dhlOf<+IyiquULwGym~~vadNf5pN?&a zl$!R2WpI-vq!d$ab)HU0kyYNQ;=j(Wmcps>(BzfhVJ#jxm^XfhEAdH#r3chGZ}1Dv zcer#k;j7<$HDiCCP`!EX2izx=q=|fwT~l?=-wV0l9?GBHjym49>N)u_Pd7g8;gi+? za>H?SQ7Oq?J;0ClvERJ?jC(S893Ly+Y_+P?p>I^og#gs|!8HYDz3SZlBW|*GQVj6?XMm_x4sQ_2WkOa zr0)78c=5UCan;@vKmMqUz>;g(s_&7xyC!o+=wHdN(XqFDVI~)#%VSi|5yEB#i{7~G zrM|$NHC6Ny%x4veHbS>qm&ML@>H+^t8eaP`y+ebJQbUpk@*OU#Y?wnm-CsQ-T~X81 z^Yi)t>LKdLq5G&XlzKkCddN6(l*|A8B>6yk^*r(~b=0%=)f3B+<6RyD4$YCwHGC0x z>TihEQ_jECmfsQ?tRsTjua!$7TLIm|xd9 zeb0k>TE5Z|%p*M`s6pYVz5_xzN&hbHyWx7DgpuCj5P_#w%2D{DP?9FA^xt-q6 zSC7$Gdc@?Yz9T|CXrH9)Ew5YZYb4aO_>rYp*BQ;xa~k#Ze&paLIfaY*dwBJH|1TZM z8z$@QZ(3OzP5ZH~R6wihI{;7@H>^oz^#vE|TLQjF^4}`|pE5d*!1EwH0nJfgd!fEO zKonqb>BARX6RgThefG)VC(4PP#3q?MToA3B{-3wS0yE`%8|vFI+U*JFBdr?IO+c~+ zset zilW!h|9+yRzNRB_&PmHLd7XG=p$M(hL&;XqnjBfns!o+$*PefTxhJT;nPcbV7j3IQ z%{=%er|u~J5&hYz@9NMcHb3~F``qnvgf_*ZGjcTVR1OcK*y!2jK=ViQ93oeZ=G{!7 z%74#|YDpCW!=XzV_pz#S@x4Rs$;!V)Ewx*P8Onv^L1swjraHcL&&J~F)boC zZUkP+*RJ2`x=$Jv)T_snEksA$0-FKk?239cBVO(`a!Ke)CmVOrWPse7y()dG?sF}G z7h8zIeZOuL+bC&tA1W8U!f{8aUF}5aw-7mEqX`3NFRl2xU+8P2_}|^wS6Tv>{n7 z;v$}&5#=Opa#n}6d9ZF zuZDSqwP@%6jULtZ6an0%2=b)nqK#=ry!s38XNuyp$BB*Yu}?y2e7iNJD55fhQ{9x! z22UO)+Sz=eQ&F@G#hp5eb-0t2Xi|;d{De$(lj4RO`uy3CqO+81N$-PFaw*Bz`78CT zzOE;2Df#j|u^CP%(bXVt0l}6NFc=02NuO#=bBl~ij2af3 zFeCyF7-4k73sI5CG^Rx*Cg6>u^b)XyG`t+iEtOs~Hbm0W_uF}-)PLl_s-C<`py<;2 zt6os$>yF)|9j5GnIO7m{Z7bfv7EN#VA|Z?s@w_BPwD_3xoB{dT@u+ieqE~0bw zXrm%BT2<40VWwEuzO1dO%QB`{{1h&2PLU^J$dB9qB!#@Ob{aLxN#1;qh{bq`z}sXK z=yi?h39V7Ek!6l zk|Nf^6CFfrZM!dukPKfaG6=sJi*d52jc8I+(HmvTSOJ#2X(hTg{bC~Ie<3IgalxwE z)QfB?eR)&LhZi*!?VRY9XA!6-@0!0vqdcP|rST9C(ZPZ)xKN0Yo3Erq6?v0LHKmuN zxp{I&7tytvdijP7HOZZQVC}u*#ObzWCK(hcL%DfvSwBX+Z^H9NiZ!^?52D$YzT?H7 gCcMyBwB2$#PwZ~W6D`GQ{N_}#(UvaL#Hzyo12`v6@c;k- delta 24515 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 diff --git a/package-lock.json b/package-lock.json index eab32c9..9283c8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@types/react": "18.2.45", "@types/react-dom": "18.2.18", "autoprefixer": "10.4.15", + "bcrypt": "^5.1.1", "cross-env": "^7.0.3", "cross-fetch": "^4.0.0", "dotenv": "^16.3.1", @@ -189,6 +190,25 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, "node_modules/@next/env": { "version": "14.0.4", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.4.tgz", @@ -882,6 +902,11 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -966,6 +991,24 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -1187,6 +1230,19 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1356,6 +1412,14 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -1377,6 +1441,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -1403,6 +1475,11 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -1498,6 +1575,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1506,6 +1588,14 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2222,6 +2312,28 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2270,6 +2382,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/get-intrinsic": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", @@ -2514,6 +2646,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -2724,6 +2861,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", @@ -3196,6 +3341,28 @@ "node": ">=12" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3235,6 +3402,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -3356,6 +3554,11 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -3380,6 +3583,20 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3396,6 +3613,18 @@ "node": ">=0.10.0" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4089,6 +4318,19 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4253,6 +4495,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -4288,6 +4549,11 @@ "node": ">=10" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4320,6 +4586,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4363,6 +4634,32 @@ "node": ">=10.0.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "node_modules/string.prototype.matchall": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.9.tgz", @@ -4581,6 +4878,33 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4971,6 +5295,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -5001,4 +5333,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index e37564b..ee7cd40 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@types/react": "18.2.45", "@types/react-dom": "18.2.18", "autoprefixer": "10.4.15", + "bcrypt": "^5.1.1", "cross-env": "^7.0.3", "cross-fetch": "^4.0.0", "dotenv": "^16.3.1", @@ -35,4 +36,4 @@ "devDependencies": { "prisma": "^5.2.0" } -} \ No newline at end of file +} diff --git a/prisma/migrations/20240618125202_add_username_to_user/migration.sql b/prisma/migrations/20240618125202_add_username_to_user/migration.sql new file mode 100644 index 0000000..aeaf1db --- /dev/null +++ b/prisma/migrations/20240618125202_add_username_to_user/migration.sql @@ -0,0 +1,14 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "username" TEXT NOT NULL, + "password" TEXT NOT NULL, + "role" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4230133..4865a97 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -31,3 +31,11 @@ model SyncLog { @@unique([timestamp, sourceHost, destinationHost]) } +model User { + id Int @id @default(autoincrement()) + username String @unique + password String + role String // "admin" or "user" + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/scripts/createAdmin.ts b/scripts/createAdmin.ts new file mode 100644 index 0000000..6ec3b0c --- /dev/null +++ b/scripts/createAdmin.ts @@ -0,0 +1,28 @@ +const { PrismaClient } = require('@prisma/client'); +const bcrypt = require('bcrypt'); + +const prisma = new PrismaClient(); + +async function createAdminUser() { + const username = 'metamanageradmin'; + const password = 'securepassword123'; // Replace with a strong password + + const hashedPassword = await bcrypt.hash(password, 10); + + const existingUser = await prisma.user.findUnique({ where: { username } }); + + if (!existingUser) { + await prisma.user.create({ + data: { + username, + password: hashedPassword, + role: 'admin', + }, + }); + } +} + +createAdminUser() + .then(() => console.log('Admin user created')) + .catch((error) => console.error('Failed to create admin user', error)) + .finally(() => prisma.$disconnect());