diff --git a/apps/client/.babelrc.json b/apps/client/.babelrc.json deleted file mode 100644 index 86efa43b..00000000 --- a/apps/client/.babelrc.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "presets": [ - "@babel/preset-typescript", - "@babel/preset-env", - [ - "@nrwl/react/babel", - { - "runtime": "automatic", - "useBuiltIns": "usage" - } - ] - ], - "plugins": [] -} diff --git a/apps/client/.storybook/main.js b/apps/client/.storybook/main.js deleted file mode 100644 index 2484425c..00000000 --- a/apps/client/.storybook/main.js +++ /dev/null @@ -1,18 +0,0 @@ -const rootMain = require('../../../.storybook/main') - -module.exports = { - ...rootMain, - core: { ...rootMain.core, builder: 'webpack5' }, - stories: ['../**/*.stories.@(js|jsx|ts|tsx)'], - addons: [...rootMain.addons, '@nrwl/react/plugins/storybook'], - webpackFinal: async (config, { configType }) => { - // apply any global webpack configs that might have been specified in .storybook/main.js - if (rootMain.webpackFinal) { - config = await rootMain.webpackFinal(config, { configType }) - } - - // add your own webpack tweaks if needed - - return config - }, -} diff --git a/apps/client/components/Maintenance.stories.tsx b/apps/client/components/Maintenance.stories.tsx deleted file mode 100644 index 468d8d2a..00000000 --- a/apps/client/components/Maintenance.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Story, Meta } from '@storybook/react' -import Maintenance from './Maintenance.tsx' -import React from 'react' - -export default { - title: 'components/Maintenance.tsx', - component: Maintenance, -} as Meta - -const Template: Story = () => { - return ( - <> - - - ) -} - -export const Base = Template.bind({}) diff --git a/apps/client/pages/api/auth/[...nextauth].ts b/apps/client/pages/api/auth/[...nextauth].ts index 485d75cb..96fd8d32 100644 --- a/apps/client/pages/api/auth/[...nextauth].ts +++ b/apps/client/pages/api/auth/[...nextauth].ts @@ -37,7 +37,7 @@ async function validateCredentials(credentials: any): Promise { - const { firstName, lastName, email, password, isAdmin } = credentials + const { firstName, lastName, email, password, role } = credentials if (!firstName || !lastName) { throw new Error('Both first name and last name are required.') } const isDevelopment = process.env.NODE_ENV === 'development' + + let userRole: AuthUserRole + + if (role === AuthUserRole.admin && isDevelopment) { + userRole = AuthUserRole.admin + } else if (role === AuthUserRole.ci) { + userRole = AuthUserRole.ci + } else { + userRole = AuthUserRole.user + } + const hashedPassword = await bcrypt.hash(password, 10) return createAuthUser({ firstName, @@ -69,7 +80,7 @@ async function createNewAuthUser(credentials: { name: `${firstName} ${lastName}`, email, password: hashedPassword, - role: isAdmin && isDevelopment ? AuthUserRole.admin : AuthUserRole.user, + role: userRole, }) } @@ -99,14 +110,11 @@ export const authOptions = { lastName: { label: 'Last name', type: 'text', placeholder: 'Last name' }, email: { label: 'Email', type: 'email', placeholder: 'hello@maybe.co' }, password: { label: 'Password', type: 'password' }, - isAdmin: { label: 'Admin', type: 'checkbox' }, + role: { label: 'Admin', type: 'text' }, }, async authorize(credentials) { - const { firstName, lastName, email, password, isAdmin } = await validateCredentials( - { - ...credentials, - isAdmin: Boolean(credentials?.isAdmin), - } + const { firstName, lastName, email, password, role } = await validateCredentials( + credentials ) const existingUser = await getAuthUserByEmail(email) @@ -123,7 +131,7 @@ export const authOptions = { throw new Error('Invalid credentials provided.') } - return createNewAuthUser({ firstName, lastName, email, password, isAdmin }) + return createNewAuthUser({ firstName, lastName, email, password, role }) }, }), ], diff --git a/apps/client/pages/register.tsx b/apps/client/pages/register.tsx index 6a0a64f5..fed963a0 100644 --- a/apps/client/pages/register.tsx +++ b/apps/client/pages/register.tsx @@ -15,7 +15,7 @@ export default function RegisterPage() { const [isValid, setIsValid] = useState(false) const [errorMessage, setErrorMessage] = useState(null) const [isLoading, setIsLoading] = useState(false) - const [isAdmin, setIsAdmin] = useState(false) + const [isAdmin, setIsAdmin] = useState(false) const { data: session } = useSession() const router = useRouter() @@ -39,7 +39,7 @@ export default function RegisterPage() { password, firstName, lastName, - isAdmin, + role: isAdmin ? 'admin' : 'user', redirect: false, }) diff --git a/apps/client/tsconfig.json b/apps/client/tsconfig.json index b836c388..5f8e9471 100644 --- a/apps/client/tsconfig.json +++ b/apps/client/tsconfig.json @@ -27,14 +27,7 @@ "next-env.d.ts", ".next/types/**/*.ts" ], - "exclude": [ - "node_modules", - "jest.config.ts", - "**/*.stories.ts", - "**/*.stories.js", - "**/*.stories.jsx", - "**/*.stories.tsx" - ], + "exclude": ["node_modules", "jest.config.ts"], "references": [ { "path": "./.storybook/tsconfig.json" diff --git a/apps/e2e/src/support/commands.ts b/apps/e2e/src/support/commands.ts index 676fb30b..be619a25 100644 --- a/apps/e2e/src/support/commands.ts +++ b/apps/e2e/src/support/commands.ts @@ -4,6 +4,7 @@ declare namespace Cypress { interface Chainable { login(): Chainable apiRequest(...params: Parameters): Chainable + nextApiRequest(...params: Parameters): Chainable getByTestId(...parameters: Parameters): Chainable selectDate(date: Date): Chainable preserveAccessToken(): Chainable @@ -28,9 +29,22 @@ Cypress.Commands.add('apiRequest', ({ url, headers = {}, ...options }, ...rest) ) }) +Cypress.Commands.add('nextApiRequest', ({ url, headers = {}, ...options }, ...rest) => { + return cy.request( + { + url: `${Cypress.env('NEXTAUTH_URL')}/${url}`, + headers: { + ...headers, + }, + ...options, + }, + ...rest + ) +}) + Cypress.Commands.add('login', () => { cy.visit('/login') - cy.get('input[name="email"]').type('bond@007.com') + cy.get('input[name="email"]').type('test@test.com') cy.get('input[name="password"]').type('TestPassword123') cy.get('button[type="submit"]').click() //eslint-disable-next-line cypress/no-unnecessary-waiting diff --git a/apps/e2e/src/support/e2e.ts b/apps/e2e/src/support/e2e.ts index be528804..896a1612 100644 --- a/apps/e2e/src/support/e2e.ts +++ b/apps/e2e/src/support/e2e.ts @@ -1,10 +1,36 @@ import './commands' beforeEach(() => { - // Login - cy.login() + cy.request({ + method: 'GET', + url: 'api/auth/csrf', + }).then((response) => { + let csrfCookies = response.headers['set-cookie'] + if (Array.isArray(csrfCookies) && csrfCookies.length > 1) { + csrfCookies = csrfCookies.map((cookie) => cookie.split(';')[0]).join('; ') + } + const csrfToken = response.body.csrfToken.trim() - // Delete the current user to wipe all data before test + cy.request({ + method: 'POST', + form: true, + headers: { + Cookie: `${csrfCookies}`, + }, + url: `api/auth/callback/credentials`, + body: { + email: 'test@test.com', + firstName: 'Test', + lastName: 'User', + password: 'TestPassword123', + role: 'ci', + csrfToken: csrfToken, + json: 'true', + }, + }).then((response) => { + expect(response.status).to.equal(200) + }) + }) cy.apiRequest({ method: 'POST', url: 'e2e/reset', @@ -12,7 +38,5 @@ beforeEach(() => { }).then((response) => { expect(response.status).to.equal(200) }) - - // Go back to dashboard cy.visit('/') }) diff --git a/apps/server/src/app/routes/e2e.router.ts b/apps/server/src/app/routes/e2e.router.ts index 5b9edb29..c82bcfcc 100644 --- a/apps/server/src/app/routes/e2e.router.ts +++ b/apps/server/src/app/routes/e2e.router.ts @@ -1,4 +1,5 @@ import type { OnboardingState } from '@maybe-finance/server/features' +import { AuthUserRole } from '@prisma/client' import { Router } from 'express' import { DateTime } from 'luxon' import { z } from 'zod' @@ -6,13 +7,13 @@ import endpoint from '../lib/endpoint' const router = Router() -const testUserId = 'test_ec3ee8a4-fa01-4f11-8ac5-9c49dd7fbae4' - router.use((req, res, next) => { - if (req.user?.sub === testUserId) { + const role = req.user?.role + + if (role === AuthUserRole.admin || role === AuthUserRole.ci) { next() } else { - res.status(401).send('Route only available to test users') + res.status(401).send('Route only available to CIUser and Admin roles') } }) @@ -47,14 +48,15 @@ router.post( trialLapsed: z.boolean().default(false), }), resolve: async ({ ctx, input }) => { + const user = ctx.user! await ctx.prisma.$transaction([ - ctx.prisma.$executeRaw`DELETE FROM "user" WHERE auth_id=${testUserId};`, + ctx.prisma.$executeRaw`DELETE FROM "user" WHERE auth_id=${user.authId};`, ctx.prisma.user.create({ data: { - authId: testUserId, - email: 'bond@007.com', - firstName: 'James', - lastName: 'Bond', + authId: user.authId, + email: user.email, + firstName: user.firstName, + lastName: user.lastName, dob: new Date('1990-01-01'), linkAccountDismissedAt: new Date(), // ensures our auto-account link doesn't trigger diff --git a/libs/server/features/src/account/insight.service.ts b/libs/server/features/src/account/insight.service.ts index 116e588e..0e6f8d62 100644 --- a/libs/server/features/src/account/insight.service.ts +++ b/libs/server/features/src/account/insight.service.ts @@ -687,7 +687,7 @@ export class InsightService implements IInsightService { INNER JOIN security s ON s.id = h.security_id LEFT JOIN LATERAL ( SELECT - asset_class AS "category" + s.asset_class AS "category" ) x ON TRUE WHERE h.account_id IN ${accountIds} diff --git a/prisma/seed.ts b/prisma/seed.ts index a8716316..0886640a 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -35,23 +35,7 @@ async function main() { }, ] - const hashedPassword = await bcrypt.hash('TestPassword123', 10) - await prisma.$transaction([ - // create testing auth user - prisma.authUser.upsert({ - where: { - id: 'test_ec3ee8a4-fa01-4f11-8ac5-9c49dd7fbae4', - }, - create: { - id: 'test_ec3ee8a4-fa01-4f11-8ac5-9c49dd7fbae4', - firstName: 'James', - lastName: 'Bond', - email: 'bond@007.com', - password: hashedPassword, - }, - update: {}, - }), // create institution linked to provider institutions ...institutions.map(({ id, name, providers }) => prisma.institution.upsert({ diff --git a/workspace.json b/workspace.json index 64b2eb15..ef681dc4 100644 --- a/workspace.json +++ b/workspace.json @@ -66,33 +66,6 @@ "options": { "command": "node tools/scripts/triggerClientDeploy.js" } - }, - "storybook": { - "executor": "@nrwl/storybook:storybook", - "options": { - "uiFramework": "@storybook/react", - "port": 4400, - "configDir": "apps/client/.storybook" - }, - "configurations": { - "ci": { - "quiet": true - } - } - }, - "build-storybook": { - "executor": "@nrwl/storybook:build", - "outputs": ["{options.outputDir}"], - "options": { - "uiFramework": "@storybook/react", - "outputDir": "dist/storybook/client", - "configDir": "apps/client/.storybook" - }, - "configurations": { - "ci": { - "quiet": true - } - } } }, "tags": ["scope:app"]