From d7f6bdf417fc08815bfba819ccd874db8a8b2f72 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Fri, 28 Jul 2023 18:51:44 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20basic=20credentials=20authent?= =?UTF-8?q?ication?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 10 +- prisma/db.sqlite | Bin 0 -> 53248 bytes prisma/schema.prisma | 63 +++++ src/pages/auth/[...nextauth].ts | 7 + src/server/api/trpc.ts | 41 ++- src/server/auth.ts | 197 ++++++++++++++ src/server/db.ts | 14 + src/utils/security.ts | 5 + src/utils/session.ts | 11 + src/validations/user.ts | 12 + yarn.lock | 446 +++++++++++++++++++++++++++++++- 11 files changed, 794 insertions(+), 12 deletions(-) create mode 100644 prisma/db.sqlite create mode 100644 prisma/schema.prisma create mode 100644 src/pages/auth/[...nextauth].ts create mode 100644 src/server/auth.ts create mode 100644 src/server/db.ts create mode 100644 src/utils/security.ts create mode 100644 src/utils/session.ts create mode 100644 src/validations/user.ts diff --git a/package.json b/package.json index 5d1a7761a..afd0e9cfd 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,10 @@ "@mantine/modals": "^6.0.0", "@mantine/next": "^6.0.0", "@mantine/notifications": "^6.0.0", + "@next-auth/prisma-adapter": "^1.0.7", "@nivo/core": "^0.83.0", "@nivo/line": "^0.83.0", + "@prisma/client": "^5.0.0", "@react-native-async-storage/async-storage": "^1.18.1", "@t3-oss/env-nextjs": "^0.6.0", "@tabler/icons-react": "^2.18.0", @@ -53,9 +55,12 @@ "@trpc/next": "^10.29.1", "@trpc/react-query": "^10.29.1", "@trpc/server": "^10.29.1", + "@types/bcrypt": "^5.0.0", "@vitejs/plugin-react": "^4.0.0", "axios": "^1.0.0", + "bcrypt": "^5.1.0", "consola": "^3.0.0", + "cookies": "^0.8.0", "cookies-next": "^2.1.1", "dayjs": "^1.11.7", "dockerode": "^3.3.2", @@ -65,6 +70,7 @@ "i18next": "^22.5.1", "js-file-download": "^0.4.12", "next": "13.4.10", + "next-auth": "^4.22.3", "next-i18next": "^13.0.0", "nzbget-api": "^0.0.3", "prismjs": "^1.29.0", @@ -86,6 +92,7 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@trivago/prettier-plugin-sort-imports": "^4.2.0", + "@types/cookies": "^0.7.7", "@types/dockerode": "^3.3.9", "@types/node": "18.16.19", "@types/prismjs": "^1.26.0", @@ -107,6 +114,7 @@ "happy-dom": "^10.0.0", "node-mocks-http": "^1.12.2", "prettier": "^3.0.0", + "prisma": "^5.0.0", "sass": "^1.56.1", "ts-node": "latest", "turbo": "latest", @@ -220,4 +228,4 @@ ] } } -} \ No newline at end of file +} diff --git a/prisma/db.sqlite b/prisma/db.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..e2af177f1f33b67707fbe3c3d957622674af9420 GIT binary patch literal 53248 zcmeI%J#X7a7{GBdar8nwM5}XTy%6S=(^5M##c?O)TyxJ{4sG5O zW;D84pBvl7T2lsmup!{hQC7Y%%0&;GLsP-@jY6%yqrZCdE*cjo%1jx-97wo?aO zKQA65ER!>iTATGZbK|3W^y^4S-iEARZ!(d>L8mM#lxvuIpS)k68Gzp z%mz7foDGe^U~(}Iob&1AwOowpVnH%oIoRO0Zi`twmd-J~wX_Ydbso<=rUYaJ9p4VLhxU36_zV>%)j=-PY4a_e4Cg zPE<5w_somWQU!7TMD12fVh?Y)j7`=0XYS9c2cv$sP^jJC(X}Y)X0J}=LxtmB1e564 z34?PE!@tGL!<)t0?ymmZchPKAGO5kh`kazXyUw)Ma&jF(#eE^eXvyiMEz#z6h#NPL zJ3YJGXmxC1s_2}!KW8Mf*%S|tyH@K_Cv+BjrW&d)y4FLhYjv7dPnaQtDJ%V1Q0*~0R#|0 z009ILKp;l~y#LS9Vi76=2q1s}0tg_000IagfB*s+1$h6T5sqdEAb; +interface CreateContextOptions { + session: Session | null; +} /** * This helper generates the "internals" for a tRPC context. If you need to use it, you can export @@ -31,7 +36,9 @@ type CreateContextOptions = Record; * * @see https://create.t3.gg/en/usage/trpc#-serverapitrpcts */ -const createInnerTRPCContext = (opts: CreateContextOptions) => ({}); +const createInnerTRPCContext = (opts: CreateContextOptions) => ({ + session: opts.session, +}); /** * This is the actual context you will use in your router. It will be used to process every request @@ -43,8 +50,11 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => { const { req, res } = opts; // Get the session from the server using the getServerSession wrapper function + const session = await getServerAuthSession({ req, res }); - return createInnerTRPCContext({}); + return createInnerTRPCContext({ + session, + }); }; /** @@ -90,3 +100,26 @@ export const createTRPCRouter = t.router; * are logged in. */ export const publicProcedure = t.procedure; + +/** Reusable middleware that enforces users are logged in before running the procedure. */ +const enforceUserIsAuthed = t.middleware(({ ctx, next }) => { + if (!ctx.session?.user) { + throw new TRPCError({ code: 'UNAUTHORIZED' }); + } + return next({ + ctx: { + // infers the `session` as non-nullable + session: { ...ctx.session, user: ctx.session.user }, + }, + }); +}); + +/** + * Protected (authenticated) procedure + * + * If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies + * the session is valid and guarantees `ctx.session.user` is not null. + * + * @see https://trpc.io/docs/procedures + */ +export const protectedProcedure = t.procedure.use(enforceUserIsAuthed); diff --git a/src/server/auth.ts b/src/server/auth.ts new file mode 100644 index 000000000..bfd95d7d6 --- /dev/null +++ b/src/server/auth.ts @@ -0,0 +1,197 @@ +import { PrismaAdapter } from '@next-auth/prisma-adapter'; +import bcrypt from 'bcrypt'; +import Cookies from 'cookies'; +import { type GetServerSidePropsContext, type NextApiRequest, type NextApiResponse } from 'next'; +import { type DefaultSession, type NextAuthOptions, getServerSession } from 'next-auth'; +import { decode, encode } from 'next-auth/jwt'; +import Credentials from 'next-auth/providers/credentials'; +import { prisma } from '~/server/db'; +import { fromDate, generateSessionToken } from '~/utils/session'; +import { signInSchema } from '~/validations/user'; + +/** + * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` + * object and keep type safety. + * + * @see https://next-auth.js.org/getting-started/typescript#module-augmentation + */ +declare module 'next-auth' { + interface Session extends DefaultSession { + user: DefaultSession['user'] & { + id: string; + isAdmin: boolean; + // ...other properties + // role: UserRole; + }; + } + + interface User { + isAdmin: boolean; + // ...other properties + // role: UserRole; + } +} + +declare module 'next-auth/jwt' { + interface JWT { + id: string; + isAdmin: boolean; + } +} + +const adapter = PrismaAdapter(prisma); +const sessionMaxAgeInSeconds = 30 * 24 * 60 * 60; // 30 days + +/** + * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. + * + * @see https://next-auth.js.org/configuration/options + */ +export const constructAuthOptions = ( + req: NextApiRequest, + res: NextApiResponse +): NextAuthOptions => ({ + callbacks: { + async session({ session, user }) { + if (session.user) { + // eslint-disable-next-line no-param-reassign + session.user.id = user.id; + // eslint-disable-next-line no-param-reassign + session.user.name = user.name as string; + + const userFromDatabase = await prisma.user.findUniqueOrThrow({ + where: { + id: user.id, + }, + include: { + roles: true, + }, + }); + + session.user.isAdmin = userFromDatabase.isAdmin; + } + + return session; + }, + async signIn({ user }) { + // Check if this sign in callback is being called in the credentials authentication flow. + // If so, use the next-auth adapter to create a session entry in the database + // (SignIn is called after authorize so we can safely assume the user is valid and already authenticated). + if (!isCredentialsRequest(req)) return true; + + if (!user) return true; + + const sessionToken = generateSessionToken(); + const sessionExpiry = fromDate(sessionMaxAgeInSeconds); + + await adapter.createSession({ + sessionToken: sessionToken, + userId: user.id, + expires: sessionExpiry, + }); + + const cookies = new Cookies(req, res); + cookies.set('next-auth.session-token', sessionToken, { + expires: sessionExpiry, + }); + + return true; + }, + }, + session: { + strategy: 'database', + maxAge: sessionMaxAgeInSeconds, + }, + adapter: PrismaAdapter(prisma), + providers: [ + Credentials({ + name: 'credentials', + credentials: { + name: { + label: 'Username', + type: 'text', + }, + password: { label: 'Password', type: 'password' }, + }, + async authorize(credentials) { + const data = await signInSchema.parseAsync(credentials); + + const user = await prisma.user.findFirst({ + include: { + roles: true, + }, + where: { + name: data.name, + }, + }); + + if (!user || !user.password) { + return null; + } + + console.log(`user ${user.id} is trying to log in. checking password...`); + const isValidPassword = await bcrypt.compare(data.password, user.password); + + if (!isValidPassword) { + console.log(`password for user ${user.id} was incorrect`); + return null; + } + + console.log(`user ${user.id} successfully authorized`); + + return { + id: user.id, + name: user.name, + isAdmin: false, + }; + }, + }), + ], + jwt: { + async encode(params) { + if (!isCredentialsRequest(req)) { + return encode(params); + } + + const cookies = new Cookies(req, res); + const cookie = cookies.get('next-auth.session-token'); + return cookie ?? ''; + }, + + async decode(params) { + if (!isCredentialsRequest(req)) { + return decode(params); + } + + return null; + }, + }, +}); + +const isCredentialsRequest = (req: NextApiRequest): boolean => { + const nextAuthQueryParams = req.query.nextauth as ['callback', 'credentials']; + return ( + nextAuthQueryParams.includes('callback') && + nextAuthQueryParams.includes('credentials') && + req.method === 'POST' + ); +}; + +/** + * Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file. + * + * @see https://next-auth.js.org/configuration/nextjs + */ +export const getServerAuthSession = (ctx: { + req: GetServerSidePropsContext['req']; + res: GetServerSidePropsContext['res']; +}) => { + return getServerSession( + ctx.req, + ctx.res, + constructAuthOptions( + ctx.req as unknown as NextApiRequest, + ctx.res as unknown as NextApiResponse + ) + ); +}; diff --git a/src/server/db.ts b/src/server/db.ts new file mode 100644 index 000000000..963fba477 --- /dev/null +++ b/src/server/db.ts @@ -0,0 +1,14 @@ +import { PrismaClient } from '@prisma/client'; +import { env } from '~/env'; + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined; +}; + +export const prisma = + globalForPrisma.prisma ?? + new PrismaClient({ + log: env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'], + }); + +if (env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma; diff --git a/src/utils/security.ts b/src/utils/security.ts new file mode 100644 index 000000000..395e9d29c --- /dev/null +++ b/src/utils/security.ts @@ -0,0 +1,5 @@ +import bcrypt from "bcrypt"; + +export const hashPassword = (password: string, salt: string) => { + return bcrypt.hashSync(password, salt); +}; diff --git a/src/utils/session.ts b/src/utils/session.ts new file mode 100644 index 000000000..976322467 --- /dev/null +++ b/src/utils/session.ts @@ -0,0 +1,11 @@ +import { randomUUID } from "crypto"; + +export const fromDate = (seconds: number, date = Date.now()) => { + return new Date(date + seconds * 1000); +}; + +// Helper functions to generate unique keys and calculate the expiry dates for session cookies +export const generateSessionToken = () => { + return randomUUID(); +}; + diff --git a/src/validations/user.ts b/src/validations/user.ts new file mode 100644 index 000000000..edb59445f --- /dev/null +++ b/src/validations/user.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +export const signInSchema = z.object({ + name: z.string(), + password: z.string(), +}); + +export const signUpFormSchema = z.object({ + username: z.string(), + password: z.string().min(8), + acceptTos: z.boolean(), +}); diff --git a/yarn.lock b/yarn.lock index c700e7e4c..636cd5b95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1123,6 +1123,35 @@ __metadata: languageName: node linkType: hard +"@mapbox/node-pre-gyp@npm:^1.0.10": + version: 1.0.11 + resolution: "@mapbox/node-pre-gyp@npm:1.0.11" + 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 + checksum: b848f6abc531a11961d780db813cc510ca5a5b6bf3184d72134089c6875a91c44d571ba6c1879470020803f7803609e7b2e6e429651c026fe202facd11d444b8 + languageName: node + linkType: hard + +"@next-auth/prisma-adapter@npm:^1.0.7": + version: 1.0.7 + resolution: "@next-auth/prisma-adapter@npm:1.0.7" + peerDependencies: + "@prisma/client": ">=2.26.0 || >=3" + next-auth: ^4 + checksum: dea44185c0de109b4b2f0a9fd84c0cab40b248c70bc9f44af516d895d22442a69fd7d85f7894e47fa506f122a5c6631b7002b0371520970289eb9d7618709e06 + languageName: node + linkType: hard + "@next/bundle-analyzer@npm:^13.0.0": version: 13.4.10 resolution: "@next/bundle-analyzer@npm:13.4.10" @@ -1427,6 +1456,13 @@ __metadata: languageName: node linkType: hard +"@panva/hkdf@npm:^1.0.2": + version: 1.1.1 + resolution: "@panva/hkdf@npm:1.1.1" + checksum: f0dd12903751d8792420353f809ed3c7de860cf506399759fff5f59f7acfef8a77e2b64012898cee7e5b047708fa0bd91dff5ef55a502bf8ea11aad9842160da + languageName: node + linkType: hard + "@pkgr/utils@npm:^2.3.1": version: 2.4.1 resolution: "@pkgr/utils@npm:2.4.1" @@ -1448,6 +1484,34 @@ __metadata: languageName: node linkType: hard +"@prisma/client@npm:^5.0.0": + version: 5.0.0 + resolution: "@prisma/client@npm:5.0.0" + dependencies: + "@prisma/engines-version": 4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584 + peerDependencies: + prisma: "*" + peerDependenciesMeta: + prisma: + optional: true + checksum: 332c2af44e880ffc9dd1223992bf6f45910094c7a3a540cbbfdda45d6caf3e82998376338abdf85e34a12f1082ae932f2385d6396c62fe4bba7ec6b84de54466 + languageName: node + linkType: hard + +"@prisma/engines-version@npm:4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584": + version: 4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584 + resolution: "@prisma/engines-version@npm:4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584" + checksum: 8fcbceef3b554ee7fa404bead50be5286412a097b21723272aff298b289caf2802b01b84bb85c4c9f3b592eeac114c8d6e79db083f271dc8c54f453b4a515233 + languageName: node + linkType: hard + +"@prisma/engines@npm:5.0.0": + version: 5.0.0 + resolution: "@prisma/engines@npm:5.0.0" + checksum: 31271d85c29709059f91051d4cef7acf874014ba0128b674ca2f842e5fac61d3011e9db246dfa67ba4803081d36dbc9e31492716bab677128588343c92117b2b + languageName: node + linkType: hard + "@radix-ui/number@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/number@npm:1.0.0" @@ -1998,6 +2062,25 @@ __metadata: languageName: node linkType: hard +"@types/bcrypt@npm:^5.0.0": + version: 5.0.0 + resolution: "@types/bcrypt@npm:5.0.0" + dependencies: + "@types/node": "*" + checksum: 063c32c7a519d64768dfc0169a319b8244d6a6cb50a355c93992b3c5fee1dbc236526a1111f0e7bb25abc8b0473e5f40a5edfeb8b33cad2a6ea35aa2d7d7db14 + languageName: node + linkType: hard + +"@types/body-parser@npm:*": + version: 1.19.2 + resolution: "@types/body-parser@npm:1.19.2" + dependencies: + "@types/connect": "*" + "@types/node": "*" + checksum: e17840c7d747a549f00aebe72c89313d09fbc4b632b949b2470c5cb3b1cb73863901ae84d9335b567a79ec5efcfb8a28ff8e3f36bc8748a9686756b6d5681f40 + languageName: node + linkType: hard + "@types/cacheable-request@npm:^6.0.1": version: 6.0.3 resolution: "@types/cacheable-request@npm:6.0.3" @@ -2026,6 +2109,15 @@ __metadata: languageName: node linkType: hard +"@types/connect@npm:*": + version: 3.4.35 + resolution: "@types/connect@npm:3.4.35" + dependencies: + "@types/node": "*" + checksum: fe81351470f2d3165e8b12ce33542eef89ea893e36dd62e8f7d72566dfb7e448376ae962f9f3ea888547ce8b55a40020ca0e01d637fab5d99567673084542641 + languageName: node + linkType: hard + "@types/cookie@npm:^0.4.1": version: 0.4.1 resolution: "@types/cookie@npm:0.4.1" @@ -2033,6 +2125,18 @@ __metadata: languageName: node linkType: hard +"@types/cookies@npm:^0.7.7": + version: 0.7.7 + resolution: "@types/cookies@npm:0.7.7" + dependencies: + "@types/connect": "*" + "@types/express": "*" + "@types/keygrip": "*" + "@types/node": "*" + checksum: d3759efc1182cb0651808570ae13638677b67b0ea724eef7b174e58ffe6ea044b62c7c2715e532f76f88fce4dd8101ed32ac6fbb73226db654017924e8a2a1e6 + languageName: node + linkType: hard + "@types/d3-color@npm:^2.0.0": version: 2.0.3 resolution: "@types/d3-color@npm:2.0.3" @@ -2134,6 +2238,30 @@ __metadata: languageName: node linkType: hard +"@types/express-serve-static-core@npm:^4.17.33": + version: 4.17.35 + resolution: "@types/express-serve-static-core@npm:4.17.35" + dependencies: + "@types/node": "*" + "@types/qs": "*" + "@types/range-parser": "*" + "@types/send": "*" + checksum: cc8995d10c6feda475ec1b3a0e69eb0f35f21ab6b49129ad5c6f279e0bc5de8175bc04ec51304cb79a43eec3ed2f5a1e01472eb6d5f827b8c35c6ca8ad24eb6e + languageName: node + linkType: hard + +"@types/express@npm:*": + version: 4.17.17 + resolution: "@types/express@npm:4.17.17" + dependencies: + "@types/body-parser": "*" + "@types/express-serve-static-core": ^4.17.33 + "@types/qs": "*" + "@types/serve-static": "*" + checksum: 0196dacc275ac3ce89d7364885cb08e7fb61f53ca101f65886dbf1daf9b7eb05c0943e2e4bbd01b0cc5e50f37e0eea7e4cbe97d0304094411ac73e1b7998f4da + languageName: node + linkType: hard + "@types/hoist-non-react-statics@npm:^3.3.1": version: 3.3.1 resolution: "@types/hoist-non-react-statics@npm:3.3.1" @@ -2151,6 +2279,13 @@ __metadata: languageName: node linkType: hard +"@types/http-errors@npm:*": + version: 2.0.1 + resolution: "@types/http-errors@npm:2.0.1" + checksum: 3bb0c50b0a652e679a84c30cd0340d696c32ef6558518268c238840346c077f899315daaf1c26c09c57ddd5dc80510f2a7f46acd52bf949e339e35ed3ee9654f + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -2200,6 +2335,13 @@ __metadata: languageName: node linkType: hard +"@types/keygrip@npm:*": + version: 1.0.2 + resolution: "@types/keygrip@npm:1.0.2" + checksum: 60bc2738a4f107070ee3d96f44709cb38f3a96c7ccabab09f56c1b2b4d85f869fd8fb9f1f2937e863d0e9e781f005c2223b823bf32b859185b4f52370c352669 + languageName: node + linkType: hard + "@types/keyv@npm:^3.1.4": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" @@ -2209,6 +2351,20 @@ __metadata: languageName: node linkType: hard +"@types/mime@npm:*": + version: 3.0.1 + resolution: "@types/mime@npm:3.0.1" + checksum: 4040fac73fd0cea2460e29b348c1a6173da747f3a87da0dbce80dd7a9355a3d0e51d6d9a401654f3e5550620e3718b5a899b2ec1debf18424e298a2c605346e7 + languageName: node + linkType: hard + +"@types/mime@npm:^1": + version: 1.3.2 + resolution: "@types/mime@npm:1.3.2" + checksum: 0493368244cced1a69cb791b485a260a422e6fcc857782e1178d1e6f219f1b161793e9f87f5fae1b219af0f50bee24fcbe733a18b4be8fdd07a38a8fb91146fd + languageName: node + linkType: hard + "@types/node@npm:*": version: 20.2.5 resolution: "@types/node@npm:20.2.5" @@ -2258,6 +2414,20 @@ __metadata: languageName: node linkType: hard +"@types/qs@npm:*": + version: 6.9.7 + resolution: "@types/qs@npm:6.9.7" + checksum: 7fd6f9c25053e9b5bb6bc9f9f76c1d89e6c04f7707a7ba0e44cc01f17ef5284adb82f230f542c2d5557d69407c9a40f0f3515e8319afd14e1e16b5543ac6cdba + languageName: node + linkType: hard + +"@types/range-parser@npm:*": + version: 1.2.4 + resolution: "@types/range-parser@npm:1.2.4" + checksum: b7c0dfd5080a989d6c8bb0b6750fc0933d9acabeb476da6fe71d8bdf1ab65e37c136169d84148034802f48378ab94e3c37bb4ef7656b2bec2cb9c0f8d4146a95 + languageName: node + linkType: hard + "@types/react-dom@npm:^18.0.0": version: 18.2.4 resolution: "@types/react-dom@npm:18.2.4" @@ -2310,6 +2480,27 @@ __metadata: languageName: node linkType: hard +"@types/send@npm:*": + version: 0.17.1 + resolution: "@types/send@npm:0.17.1" + dependencies: + "@types/mime": ^1 + "@types/node": "*" + checksum: 10b620a5960058ef009afbc17686f680d6486277c62f640845381ec4baa0ea683fdd77c3afea4803daf5fcddd3fb2972c8aa32e078939f1d4e96f83195c89793 + languageName: node + linkType: hard + +"@types/serve-static@npm:*": + version: 1.15.2 + resolution: "@types/serve-static@npm:1.15.2" + dependencies: + "@types/http-errors": "*" + "@types/mime": "*" + "@types/node": "*" + checksum: 15c261dbfc57890f7cc17c04d5b22b418dfa0330c912b46c5d8ae2064da5d6f844ef7f41b63c7f4bbf07675e97ebe6ac804b032635ec742ae45d6f1274259b3e + languageName: node + linkType: hard + "@types/ssh2@npm:*": version: 1.11.11 resolution: "@types/ssh2@npm:1.11.11" @@ -2772,7 +2963,7 @@ __metadata: languageName: node linkType: hard -"abbrev@npm:^1.0.0": +"abbrev@npm:1, abbrev@npm:^1.0.0": version: 1.1.1 resolution: "abbrev@npm:1.1.1" checksum: a4a97ec07d7ea112c517036882b2ac22f3109b7b19077dc656316d07d308438aac28e4d9746dc4d84bf6b1e75b4a7b0a5f3cb30592419f128ca9a8cee3bcfa17 @@ -2926,6 +3117,16 @@ __metadata: languageName: node linkType: hard +"are-we-there-yet@npm:^2.0.0": + version: 2.0.0 + resolution: "are-we-there-yet@npm:2.0.0" + dependencies: + delegates: ^1.0.0 + readable-stream: ^3.6.0 + checksum: 6c80b4fd04ecee6ba6e737e0b72a4b41bdc64b7d279edfc998678567ff583c8df27e27523bc789f2c99be603ffa9eaa612803da1d886962d2086e7ff6fa90c7c + languageName: node + linkType: hard + "are-we-there-yet@npm:^3.0.0": version: 3.0.1 resolution: "are-we-there-yet@npm:3.0.1" @@ -3140,6 +3341,16 @@ __metadata: languageName: node linkType: hard +"bcrypt@npm:^5.1.0": + version: 5.1.0 + resolution: "bcrypt@npm:5.1.0" + dependencies: + "@mapbox/node-pre-gyp": ^1.0.10 + node-addon-api: ^5.0.0 + checksum: a590b65d276d75d861dc85acc3128508b8f78c87431719658ea3be7996368b34b397b6efefe6bca0a3d555bf41a9267307fd4ce04e956598fca3ba81199c6706 + languageName: node + linkType: hard + "big-integer@npm:^1.6.44": version: 1.6.51 resolution: "big-integer@npm:1.6.51" @@ -3547,7 +3758,7 @@ __metadata: languageName: node linkType: hard -"color-support@npm:^1.1.3": +"color-support@npm:^1.1.2, color-support@npm:^1.1.3": version: 1.1.3 resolution: "color-support@npm:1.1.3" bin: @@ -3600,7 +3811,7 @@ __metadata: languageName: node linkType: hard -"console-control-strings@npm:^1.1.0": +"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" checksum: 8755d76787f94e6cf79ce4666f0c5519906d7f5b02d4b884cf41e11dcd759ed69c57da0670afd9236d229a46e0f9cf519db0cd829c6dca820bb5a5c3def584ed @@ -3630,6 +3841,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^0.5.0": + version: 0.5.0 + resolution: "cookie@npm:0.5.0" + checksum: 1f4bd2ca5765f8c9689a7e8954183f5332139eb72b6ff783d8947032ec1fdf43109852c178e21a953a30c0dd42257828185be01b49d1eb1a67fd054ca588a180 + languageName: node + linkType: hard + "cookies-next@npm:^2.1.1": version: 2.1.2 resolution: "cookies-next@npm:2.1.2" @@ -3641,6 +3859,16 @@ __metadata: languageName: node linkType: hard +"cookies@npm:^0.8.0": + version: 0.8.0 + resolution: "cookies@npm:0.8.0" + dependencies: + depd: ~2.0.0 + keygrip: ~1.1.0 + checksum: 806055a44f128705265b1bc6a853058da18bf80dea3654ad99be20985b1fa1b14f86c1eef73644aab8071241f8a78acd57202b54c4c5c70769fc694fbb9c4edc + languageName: node + linkType: hard + "copy-anything@npm:^3.0.2": version: 3.0.5 resolution: "copy-anything@npm:3.0.5" @@ -4029,13 +4257,20 @@ __metadata: languageName: node linkType: hard -"depd@npm:^2.0.0": +"depd@npm:^2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: abbe19c768c97ee2eed6282d8ce3031126662252c58d711f646921c9623f9052e3e1906443066beec1095832f534e57c523b7333f8e7e0d93051ab6baef5ab3a languageName: node linkType: hard +"detect-libc@npm:^2.0.0": + version: 2.0.2 + resolution: "detect-libc@npm:2.0.2" + checksum: 2b2cd3649b83d576f4be7cc37eb3b1815c79969c8b1a03a40a4d55d83bc74d010753485753448eacb98784abf22f7dbd3911fd3b60e29fda28fed2d1a997944d + languageName: node + linkType: hard + "detect-node-es@npm:^1.1.0": version: 1.1.0 resolution: "detect-node-es@npm:1.1.0" @@ -5140,6 +5375,23 @@ __metadata: languageName: node linkType: hard +"gauge@npm:^3.0.0": + version: 3.0.2 + resolution: "gauge@npm:3.0.2" + 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 + checksum: 81296c00c7410cdd48f997800155fbead4f32e4f82109be0719c63edc8560e6579946cc8abd04205297640691ec26d21b578837fd13a4e96288ab4b40b1dc3e9 + languageName: node + linkType: hard + "gauge@npm:^4.0.3": version: 4.0.4 resolution: "gauge@npm:4.0.4" @@ -5554,10 +5806,12 @@ __metadata: "@mantine/modals": ^6.0.0 "@mantine/next": ^6.0.0 "@mantine/notifications": ^6.0.0 + "@next-auth/prisma-adapter": ^1.0.7 "@next/bundle-analyzer": ^13.0.0 "@next/eslint-plugin-next": ^13.4.5 "@nivo/core": ^0.83.0 "@nivo/line": ^0.83.0 + "@prisma/client": ^5.0.0 "@react-native-async-storage/async-storage": ^1.18.1 "@t3-oss/env-nextjs": ^0.6.0 "@tabler/icons-react": ^2.18.0 @@ -5573,6 +5827,8 @@ __metadata: "@trpc/next": ^10.29.1 "@trpc/react-query": ^10.29.1 "@trpc/server": ^10.29.1 + "@types/bcrypt": ^5.0.0 + "@types/cookies": ^0.7.7 "@types/dockerode": ^3.3.9 "@types/node": 18.16.19 "@types/prismjs": ^1.26.0 @@ -5585,7 +5841,9 @@ __metadata: "@vitest/coverage-c8": ^0.33.0 "@vitest/ui": ^0.33.0 axios: ^1.0.0 + bcrypt: ^5.1.0 consola: ^3.0.0 + cookies: ^0.8.0 cookies-next: ^2.1.1 dayjs: ^1.11.7 dockerode: ^3.3.2 @@ -5604,10 +5862,12 @@ __metadata: i18next: ^22.5.1 js-file-download: ^0.4.12 next: 13.4.10 + next-auth: ^4.22.3 next-i18next: ^13.0.0 node-mocks-http: ^1.12.2 nzbget-api: ^0.0.3 prettier: ^3.0.0 + prisma: ^5.0.0 prismjs: ^1.29.0 react: ^18.2.0 react-dom: ^18.2.0 @@ -6347,6 +6607,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^4.11.4, jose@npm:^4.14.4": + version: 4.14.4 + resolution: "jose@npm:4.14.4" + checksum: 2d820a91a8fd97c05d8bc8eedc373b944a0cd7f5fe41063086da233d0473c73fb523912a9f026ea870782bd221f4a515f441a2d3af4de48c6f2c76dac5082377 + languageName: node + linkType: hard + "js-file-download@npm:^0.4.12": version: 0.4.12 resolution: "js-file-download@npm:0.4.12" @@ -6453,6 +6720,15 @@ __metadata: languageName: node linkType: hard +"keygrip@npm:~1.1.0": + version: 1.1.0 + resolution: "keygrip@npm:1.1.0" + dependencies: + tsscmp: 1.0.6 + checksum: 078cd16a463d187121f0a27c1c9c95c52ad392b620f823431689f345a0501132cee60f6e96914b07d570105af470b96960402accd6c48a0b1f3cd8fac4fa2cae + languageName: node + linkType: hard + "keyv@npm:^4.0.0, keyv@npm:^4.5.2": version: 4.5.2 resolution: "keyv@npm:4.5.2" @@ -6620,7 +6896,7 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^3.0.0": +"make-dir@npm:^3.0.0, make-dir@npm:^3.1.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" dependencies: @@ -7028,6 +7304,31 @@ __metadata: languageName: node linkType: hard +"next-auth@npm:^4.22.3": + version: 4.22.3 + resolution: "next-auth@npm:4.22.3" + dependencies: + "@babel/runtime": ^7.20.13 + "@panva/hkdf": ^1.0.2 + cookie: ^0.5.0 + jose: ^4.11.4 + oauth: ^0.9.15 + openid-client: ^5.4.0 + preact: ^10.6.3 + preact-render-to-string: ^5.1.19 + uuid: ^8.3.2 + peerDependencies: + next: ^12.2.5 || ^13 + nodemailer: ^6.6.5 + react: ^17.0.2 || ^18 + react-dom: ^17.0.2 || ^18 + peerDependenciesMeta: + nodemailer: + optional: true + checksum: 2ab79f91c417320a514250a8ccd1753fe0779992b9d4c949c22704f2375ec30a221c72a0e817bb6372ca422c56a0a84280f6b8d97156265370ef9ca7d5636624 + languageName: node + linkType: hard + "next-i18next@npm:^13.0.0": version: 13.3.0 resolution: "next-i18next@npm:13.3.0" @@ -7105,6 +7406,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^5.0.0": + version: 5.1.0 + resolution: "node-addon-api@npm:5.1.0" + dependencies: + node-gyp: latest + checksum: 2508bd2d2981945406243a7bd31362fc7af8b70b8b4d65f869c61731800058fb818cc2fd36c8eac714ddd0e568cc85becf5e165cebbdf7b5024d5151bbc75ea1 + languageName: node + linkType: hard + "node-domexception@npm:1.0.0": version: 1.0.0 resolution: "node-domexception@npm:1.0.0" @@ -7126,6 +7436,20 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:^2.6.7": + version: 2.6.12 + resolution: "node-fetch@npm:2.6.12" + dependencies: + whatwg-url: ^5.0.0 + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 3bc1655203d47ee8e313c0d96664b9673a3d4dd8002740318e9d27d14ef306693a4b2ef8d6525775056fd912a19e23f3ac0d7111ad8925877b7567b29a625592 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 9.3.1 resolution: "node-gyp@npm:9.3.1" @@ -7178,6 +7502,17 @@ __metadata: languageName: node linkType: hard +"nopt@npm:^5.0.0": + version: 5.0.0 + resolution: "nopt@npm:5.0.0" + dependencies: + abbrev: 1 + bin: + nopt: bin/nopt.js + checksum: d35fdec187269503843924e0114c0c6533fb54bbf1620d0f28b4b60ba01712d6687f62565c55cc20a504eff0fbe5c63e22340c3fad549ad40469ffb611b04f2f + languageName: node + linkType: hard + "nopt@npm:^6.0.0": version: 6.0.0 resolution: "nopt@npm:6.0.0" @@ -7228,6 +7563,18 @@ __metadata: languageName: node linkType: hard +"npmlog@npm:^5.0.1": + version: 5.0.1 + resolution: "npmlog@npm:5.0.1" + dependencies: + are-we-there-yet: ^2.0.0 + console-control-strings: ^1.1.0 + gauge: ^3.0.0 + set-blocking: ^2.0.0 + checksum: 516b2663028761f062d13e8beb3f00069c5664925871a9b57989642ebe09f23ab02145bf3ab88da7866c4e112cafff72401f61a672c7c8a20edc585a7016ef5f + languageName: node + linkType: hard + "npmlog@npm:^6.0.0": version: 6.0.2 resolution: "npmlog@npm:6.0.2" @@ -7251,6 +7598,13 @@ __metadata: languageName: node linkType: hard +"oauth@npm:^0.9.15": + version: 0.9.15 + resolution: "oauth@npm:0.9.15" + checksum: 957c0d8d85300398dcb0e293953650c0fc3facc795bee8228238414f19f59cef5fd4ee8d17a972c142924c10c5f6ec50ef80f77f4a6cc6e3c98f9d22c027801c + languageName: node + linkType: hard + "object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -7258,6 +7612,13 @@ __metadata: languageName: node linkType: hard +"object-hash@npm:^2.2.0": + version: 2.2.0 + resolution: "object-hash@npm:2.2.0" + checksum: 55ba841e3adce9c4f1b9b46b41983eda40f854e0d01af2802d3ae18a7085a17168d6b81731d43fdf1d6bcbb3c9f9c56d22c8fea992203ad90a38d7d919bc28f1 + languageName: node + linkType: hard + "object-inspect@npm:^1.12.3, object-inspect@npm:^1.9.0": version: 1.12.3 resolution: "object-inspect@npm:1.12.3" @@ -7344,6 +7705,13 @@ __metadata: languageName: node linkType: hard +"oidc-token-hash@npm:^5.0.3": + version: 5.0.3 + resolution: "oidc-token-hash@npm:5.0.3" + checksum: 35fa19aea9ff2c509029ec569d74b778c8a215b92bd5e6e9bc4ebbd7ab035f44304ff02430a6397c3fb7c1d15ebfa467807ca0bcd31d06ba610b47798287d303 + languageName: node + linkType: hard + "once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -7392,6 +7760,18 @@ __metadata: languageName: node linkType: hard +"openid-client@npm:^5.4.0": + version: 5.4.3 + resolution: "openid-client@npm:5.4.3" + dependencies: + jose: ^4.14.4 + lru-cache: ^6.0.0 + object-hash: ^2.2.0 + oidc-token-hash: ^5.0.3 + checksum: 0e5a126b77dad0320e8f7023ac7ad7f5f1f82ad5f985f7ab0b42a7cf36700dfb78f0bef9b59c1fae915dce0148ef191b49921cd0a01443b64c04f862d9dc03e0 + languageName: node + linkType: hard + "optionator@npm:^0.9.3": version: 0.9.3 resolution: "optionator@npm:0.9.3" @@ -7598,6 +7978,24 @@ __metadata: languageName: node linkType: hard +"preact-render-to-string@npm:^5.1.19": + version: 5.2.6 + resolution: "preact-render-to-string@npm:5.2.6" + dependencies: + pretty-format: ^3.8.0 + peerDependencies: + preact: ">=10" + checksum: be8d5d8fb502d422c503e68af7bcccb6facd942f3ae9a4d093ebe3f1d4f0b15c540624bdac434d53a2a8e8fb7afa4606383414e937c40933ca43445470a026ff + languageName: node + linkType: hard + +"preact@npm:^10.6.3": + version: 10.16.0 + resolution: "preact@npm:10.16.0" + checksum: 47a91f47d583b68a4afe971a7f992c06547df6d637cadf56eb3b69fee1fb202659b199af37d0e1a90637385144cadd75aa40acdb4e125cc4b3155e2883c24c07 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -7636,6 +8034,24 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:^3.8.0": + version: 3.8.0 + resolution: "pretty-format@npm:3.8.0" + checksum: 21a114d43ef06978f8f7f6212be4649b0b094f05d9b30e14e37550bf35c8ca24d8adbca9e5adc4cc15d9eaf7a1e7a30478a4dc37b30982bfdf0292a5b385484c + languageName: node + linkType: hard + +"prisma@npm:^5.0.0": + version: 5.0.0 + resolution: "prisma@npm:5.0.0" + dependencies: + "@prisma/engines": 5.0.0 + bin: + prisma: build/index.js + checksum: fdc62377853d25b4db664c736fd0b08d2b0c6db5752e6f6c6ec3bda77634cfb79e6f49d52d4b8f54ddb8ec9c28fc3fb0c13f95caf61085447d0929e258af9284 + languageName: node + linkType: hard + "prismjs@npm:^1.29.0": version: 1.29.0 resolution: "prismjs@npm:1.29.0" @@ -8348,7 +8764,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 @@ -8982,6 +9398,13 @@ __metadata: languageName: node linkType: hard +"tsscmp@npm:1.0.6": + version: 1.0.6 + resolution: "tsscmp@npm:1.0.6" + checksum: 1512384def36bccc9125cabbd4c3b0e68608d7ee08127ceaa0b84a71797263f1a01c7f82fa69be8a3bd3c1396e2965d2f7b52d581d3a5eeaf3967fbc52e3b3bf + languageName: node + linkType: hard + "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" @@ -9308,6 +9731,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df + languageName: node + linkType: hard + "uuid@npm:^9.0.0": version: 9.0.0 resolution: "uuid@npm:9.0.0" @@ -9669,7 +10101,7 @@ __metadata: languageName: node linkType: hard -"wide-align@npm:^1.1.5": +"wide-align@npm:^1.1.2, wide-align@npm:^1.1.5": version: 1.1.5 resolution: "wide-align@npm:1.1.5" dependencies: