From d3b7f1985250172eeddf0177ddc449510bfd74a4 Mon Sep 17 00:00:00 2001 From: Sam J Combs Date: Tue, 19 Nov 2024 15:09:01 -0500 Subject: [PATCH] feat: oauth support for azure and github --- Dockerfile | 9 + backend/.eslintrc.js | 2 +- backend/src/config/config.ts | 63 ++---- backend/src/config/supertokens.providers.ts | 19 -- backend/src/config/supertokens.ts | 181 ++++++++++-------- .../sqlite/Migration20241119174820.ts | 9 + backend/src/routes/auth.ts | 26 +++ backend/src/server.ts | 2 + docker-compose.yml | 17 +- docs/oauth-setup.md | 39 ++++ frontend/src/components/ui/home.tsx | 6 +- frontend/src/components/ui/login.tsx | 172 ++++++----------- frontend/src/config.ts | 34 ++-- frontend/src/hooks/useAuthProviders.ts | 30 +++ package-lock.json | 5 +- package.json | 14 +- 16 files changed, 332 insertions(+), 296 deletions(-) delete mode 100644 backend/src/config/supertokens.providers.ts create mode 100644 backend/src/migrations/sqlite/Migration20241119174820.ts create mode 100644 backend/src/routes/auth.ts create mode 100644 docs/oauth-setup.md create mode 100644 frontend/src/hooks/useAuthProviders.ts diff --git a/Dockerfile b/Dockerfile index 1e7e107..e7a198f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,15 @@ FROM node:20-alpine AS frontend-builder WORKDIR /app/frontend +ARG REACT_APP_BACKEND_URL +ENV REACT_APP_BACKEND_URL=$REACT_APP_BACKEND_URL +ARG REACT_APP_BACKEND_PORT +ENV REACT_APP_BACKEND_PORT=$REACT_APP_BACKEND_PORT +ARG REACT_APP_FRONTEND_URL +ENV REACT_APP_FRONTEND_URL=$REACT_APP_FRONTEND_URL +ARG REACT_APP_FRONTEND_PORT +ENV REACT_APP_FRONTEND_PORT=$REACT_APP_FRONTEND_PORT + COPY frontend/package*.json ./ RUN npm install diff --git a/backend/.eslintrc.js b/backend/.eslintrc.js index 4451218..36e4edd 100644 --- a/backend/.eslintrc.js +++ b/backend/.eslintrc.js @@ -4,7 +4,7 @@ module.exports = { ecmaVersion: 2020, sourceType: 'module', }, - ignorePatterns: ['**/*.test.ts', 'dist/**'], + ignorePatterns: ['**/*.test.ts', 'dist/'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', diff --git a/backend/src/config/config.ts b/backend/src/config/config.ts index d2cfe51..28efb10 100644 --- a/backend/src/config/config.ts +++ b/backend/src/config/config.ts @@ -23,15 +23,25 @@ const config = convict({ format: 'port', default: 3033, }, - apiUrl: { - env: 'API_URL', + host: { + env: 'HOST', format: String, - default: 'http://localhost:3033', + default: '0.0.0.0', }, - websiteUrl: { - env: 'WEBSITE_URL', + backendUrl: { + env: 'BACKEND_URL', format: String, - default: 'http://localhost:3000', + default: 'http://localhost', + }, + frontendUrl: { + env: 'FRONTEND_URL', + format: String, + default: 'http://localhost', + }, + frontendPort: { + env: 'FRONTEND_PORT', + format: 'port', + default: 3033, }, }, supertokens: { @@ -58,11 +68,6 @@ const config = convict({ sensitive: true, default: null, }, - tenantId: { - env: 'AZURE_TENANT_ID', - format: '*', - default: null, - }, }, github: { clientId: { @@ -77,42 +82,6 @@ const config = convict({ default: null, }, }, - google: { - clientId: { - env: 'GOOGLE_CLIENT_ID', - format: '*', - default: null, - }, - clientSecret: { - env: 'GOOGLE_CLIENT_SECRET', - format: '*', - sensitive: true, - default: null, - }, - }, - aws: { - clientId: { - env: 'AWS_CLIENT_ID', - format: '*', - default: null, - }, - clientSecret: { - env: 'AWS_CLIENT_SECRET', - format: '*', - sensitive: true, - default: null, - }, - region: { - env: 'AWS_REGION', - format: String, - default: 'us-east-1', - }, - userPoolId: { - env: 'AWS_USER_POOL_ID', - format: '*', - default: null, - }, - }, }, }); diff --git a/backend/src/config/supertokens.providers.ts b/backend/src/config/supertokens.providers.ts deleted file mode 100644 index 59473f1..0000000 --- a/backend/src/config/supertokens.providers.ts +++ /dev/null @@ -1,19 +0,0 @@ -import config from './config'; -import {ProviderInput} from 'supertokens-node/lib/build/recipe/thirdparty/types'; - -export function getAuthProviders(): ProviderInput[] { - const providers = []; - - const githubConfig = config.get('supertokens.github'); - if (githubConfig?.clientId && githubConfig?.clientSecret) { - providers.push({ - config: { - thirdPartyId: 'github', - clientId: githubConfig.clientId, - clientSecret: githubConfig.clientSecret, - }, - }); - } - - return providers; -} diff --git a/backend/src/config/supertokens.ts b/backend/src/config/supertokens.ts index 5a53263..25686d8 100644 --- a/backend/src/config/supertokens.ts +++ b/backend/src/config/supertokens.ts @@ -5,18 +5,95 @@ import {TypeInput} from 'supertokens-node/types'; import config from './config'; export function getApiDomain(): string { - return config.get('server.apiUrl'); + const backendUrl = config.get('server.backendUrl'); + const port = config.get('server.port'); + return `${backendUrl}:${port}`; } export function getWebsiteDomain(): string { - return config.get('server.websiteUrl'); + const frontendUrl = config.get('server.frontendUrl'); + const frontendPort = config.get('server.frontendPort'); + return `${frontendUrl}:${frontendPort}`; +} + +function getGithubProvider() { + const clientId = config.get('supertokens.github.clientId'); + const clientSecret = config.get('supertokens.github.clientSecret'); + + if (!clientId || !clientSecret) { + return null; + } + + return { + config: { + thirdPartyId: 'github', + clients: [ + { + clientId, + clientSecret, + }, + ], + }, + }; +} + +function getAzureProvider() { + const clientId = config.get('supertokens.azure.clientId'); + const clientSecret = config.get('supertokens.azure.clientSecret'); + + if (!clientId || !clientSecret) { + return null; + } + + return { + config: { + thirdPartyId: 'azure', + name: 'azure', + clients: [ + { + clientId, + clientSecret, + scope: ['openid', 'email', 'profile', 'User.Read'], + }, + ], + authorizationEndpoint: + 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + tokenEndpoint: + 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + userInfoEndpoint: 'https://graph.microsoft.com/v1.0/me', + authorizationEndpointQueryParams: { + redirect_uri: `${getApiDomain()}/auth/callback/azure`, + }, + userInfoMap: { + fromUserInfoAPI: { + userId: 'id', + email: 'mail', + }, + }, + }, + }; +} + +function getConfiguredProviders() { + const providers = []; + + const githubProvider = getGithubProvider(); + if (githubProvider) { + providers.push(githubProvider); + } + + const azureProvider = getAzureProvider(); + if (azureProvider) { + providers.push(azureProvider); + } + + return providers; } export const SuperTokensConfig: TypeInput = { debug: true, supertokens: { - connectionURI: - config.get('supertokens.connectionUri') || 'http://localhost:3567', + connectionURI: config.get('supertokens.connectionUri'), }, appInfo: { appName: 'Instant Mock', @@ -28,88 +105,26 @@ export const SuperTokensConfig: TypeInput = { recipeList: [ ThirdParty.init({ override: { - functions: (originalImplementation) => { - return { - ...originalImplementation, - // override the thirdparty sign in / up API - signInUp: async function (input) { - const response = await originalImplementation.signInUp(input); - - if (response.status === 'OK') { - // TODO fix avatar for azure - const avatar = - response.rawUserInfoFromProvider.fromUserInfoAPI?.user - ?.avatar_url; - if (avatar) { - await UserMetadata.updateUserMetadata(response.user.id, { - avatarUrl: avatar, - }); - } + functions: (originalImplementation) => ({ + ...originalImplementation, + signInUp: async function (input) { + const response = await originalImplementation.signInUp(input); + if (response.status === 'OK') { + const avatar = + response.rawUserInfoFromProvider.fromUserInfoAPI?.user + ?.avatar_url; + if (avatar) { + await UserMetadata.updateUserMetadata(response.user.id, { + avatarUrl: avatar, + }); } - - return response; - }, - }; - }, + } + return response; + }, + }), }, signInAndUpFeature: { - providers: [ - { - config: { - thirdPartyId: 'github', - clients: [ - { - clientId: (() => { - const clientId = config.get('supertokens.github.clientId'); - if (!clientId) - throw new Error( - 'GITHUB_CLIENT_ID is required for authentication' - ); - return clientId; - })(), - clientSecret: (() => { - const clientSecret = config.get( - 'supertokens.github.clientSecret' - ); - if (!clientSecret) - throw new Error( - 'GITHUB_CLIENT_SECRET is required for authentication' - ); - return clientSecret; - })(), - }, - ], - }, - }, - { - config: { - thirdPartyId: 'azure', - name: 'azure', - clients: [ - { - clientId: '', - clientSecret: '', - scope: ['openid', 'email', 'profile', 'User.Read'], // Include 'openid' explicitly - }, - ], - authorizationEndpoint: - 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', - tokenEndpoint: - 'https://login.microsoftonline.com/common/oauth2/v2.0/token', // Correct endpoint - userInfoEndpoint: 'https://graph.microsoft.com/v1.0/me', - authorizationEndpointQueryParams: { - redirect_uri: 'http://localhost:3000/auth/callback/azure', // Must match Azure registration - }, - userInfoMap: { - fromUserInfoAPI: { - userId: 'id', // Maps to the Microsoft Graph user ID - email: 'mail', // Retrieves email - // emailVerified: 'email_verified', // Not directly available; needs a custom field - }, - }, - }, - }, - ], + providers: getConfiguredProviders(), }, }), Session.init(), diff --git a/backend/src/migrations/sqlite/Migration20241119174820.ts b/backend/src/migrations/sqlite/Migration20241119174820.ts new file mode 100644 index 0000000..4897d52 --- /dev/null +++ b/backend/src/migrations/sqlite/Migration20241119174820.ts @@ -0,0 +1,9 @@ +import {Migration} from '@mikro-orm/migrations'; + +export class Migration20241119174820 extends Migration { + override async up(): Promise { + this.addSql( + `alter table \`apollo_api_key\` add column \`user_id\` text not null;` + ); + } +} diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts new file mode 100644 index 0000000..685eaa3 --- /dev/null +++ b/backend/src/routes/auth.ts @@ -0,0 +1,26 @@ +import express from 'express'; +import config from '../config/config'; + +const router = express.Router(); + +router.get('/auth-providers', (_, res) => { + const providers = []; + + if (config.get('supertokens.github.clientId')) { + providers.push({ + id: 'github', + name: 'GitHub', + }); + } + + if (config.get('supertokens.azure.clientId')) { + providers.push({ + id: 'azure', + name: 'Microsoft', + }); + } + + res.json({providers}); +}); + +export default router; diff --git a/backend/src/server.ts b/backend/src/server.ts index b8e9189..16e47a2 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -21,6 +21,7 @@ import {Seed} from './models/seed'; import {SeedGroup} from './models/seedGroup'; import apolloApiKeysRoutes from './routes/apolloApiKey'; import avatarRoutes from './routes/avatar'; +import authRoutes from './routes/auth'; import graphqlRoutes from './routes/graphql'; import graphsRoutes from './routes/graphs'; import proposalsRoutes from './routes/proposals'; @@ -107,6 +108,7 @@ const initializeApp = async () => { }) ); app.use(authMiddleware.init); + app.use(authRoutes); app.use('/api', authMiddleware.verify); app.use((_, __, next) => RequestContext.create(DI.orm.em, next)); app.use('/api', seedsRoutes); diff --git a/docker-compose.yml b/docker-compose.yml index 123b26b..11e245a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,17 +4,24 @@ services: build: context: . dockerfile: Dockerfile + args: + REACT_APP_BACKEND_URL: "${BACKEND_URL:-http://localhost}" + REACT_APP_BACKEND_PORT: "${PORT:-3033}" + REACT_APP_FRONTEND_URL: "${FRONTEND_URL:-http://localhost}" + REACT_APP_FRONTEND_PORT: "${FRONTEND_PORT:-3033}" container_name: instant-mock environment: - PORT: "${PORT:-3031}" + PORT: "${PORT:-3033}" APOLLO_API_KEY: "${APOLLO_API_KEY}" NODE_ENV: "development" HOST: "0.0.0.0" SUPERTOKENS_CONNECTION_URI: "http://supertokens:3567" GITHUB_CLIENT_ID: "${GITHUB_CLIENT_ID}" GITHUB_CLIENT_SECRET: "${GITHUB_CLIENT_SECRET}" + AZURE_CLIENT_ID: "${AZURE_CLIENT_ID}" + AZURE_CLIENT_SECRET: "${AZURE_CLIENT_SECRET}" ports: - - "${PORT:-3031}:${PORT:-3031}" + - "${PORT:-3033}:${PORT:-3033}" volumes: - ./backend/data:/app/backend/data networks: @@ -22,7 +29,6 @@ services: depends_on: - supertokens - db - db: image: 'postgres:latest' container_name: supertokens_db @@ -36,11 +42,10 @@ services: - app_network restart: unless-stopped healthcheck: - test: [ 'CMD', 'pg_isready', '-U', 'supertokens_user', '-d', 'supertokens' ] + test: ['CMD', 'pg_isready', '-U', 'supertokens_user', '-d', 'supertokens'] interval: 5s timeout: 5s retries: 5 - supertokens: image: registry.supertokens.io/supertokens/supertokens-postgresql:latest container_name: supertokens @@ -57,10 +62,10 @@ services: healthcheck: test: > bash -c 'exec 3<>/dev/tcp/127.0.0.1/3567 && echo -e "GET /hello HTTP/1.1\r\nhost: 127.0.0.1:3567\r\nConnection: close\r\n\r\n" >&3 && cat <&3 | grep "Hello"' + interval: 10s timeout: 5s retries: 5 - networks: app_network: driver: bridge diff --git a/docs/oauth-setup.md b/docs/oauth-setup.md new file mode 100644 index 0000000..f04cb79 --- /dev/null +++ b/docs/oauth-setup.md @@ -0,0 +1,39 @@ +# Setting Up OAuth Providers for InstantMock + +## GitHub OAuth Setup + +1. Go to [GitHub Developer Settings](https://github.com/settings/developers) +2. Click "New OAuth App" +3. Fill in the application details: + - Application name: `InstantMock Local` + - Homepage URL: `http://localhost:3033` + - Authorization callback URL: `http://localhost:3033/auth/callback/github` +4. Click "Register application" +5. Copy the generated Client ID +6. Generate a Client Secret and copy it +7. Add to your `.env` file: + ``` + GITHUB_CLIENT_ID=your_client_id + GITHUB_CLIENT_SECRET=your_client_secret + ``` + +## Microsoft Azure OAuth Setup + +1. Go to [Azure Portal](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) +2. Click "New registration" +3. Fill in the application details: + - Name: `InstantMock Local` + - Supported account types: "Accounts in any organizational directory and personal Microsoft accounts" + - Redirect URI: `http://localhost:3033/auth/callback/azure` +4. Click "Register" +5. Copy the Application (client) ID +6. Under "Certificates & secrets", create a new client secret +7. Add to your `.env` file: + ``` + AZURE_CLIENT_ID=your_client_id + AZURE_CLIENT_SECRET=your_client_secret + ``` + +## Restart Required + +After adding the credentials to your `.env` file, restart the InstantMock server for the changes to take effect. \ No newline at end of file diff --git a/frontend/src/components/ui/home.tsx b/frontend/src/components/ui/home.tsx index 19fe010..076ef34 100644 --- a/frontend/src/components/ui/home.tsx +++ b/frontend/src/components/ui/home.tsx @@ -551,19 +551,19 @@ const Home = () => { value="sandbox" className="px-0 pb-2 pt-0 text-sm font-small border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:text-primary data-[state=active]:shadow-none rounded-none bg-transparent" > - SANDBOX + OPERATIONS - SEEDS + MOCK DATA - NARRATIVES + COLLABORATE diff --git a/frontend/src/components/ui/login.tsx b/frontend/src/components/ui/login.tsx index 7036d6e..7177ef3 100644 --- a/frontend/src/components/ui/login.tsx +++ b/frontend/src/components/ui/login.tsx @@ -1,6 +1,4 @@ 'use client'; - -import * as React from 'react'; import { getAuthorisationURLWithQueryParamsAndSetState, redirectToThirdPartyLogin, @@ -10,14 +8,19 @@ import microsoftLogo from '../../assets/microsoft_logo.svg'; import logo from '../../assets/xolvio_logo.png'; import {Button} from './button'; import {Card, CardContent} from './card'; +import {useAuthProviders} from '../../hooks/useAuthProviders'; +import {Loader2} from 'lucide-react'; +import {getApiBaseUrl} from '../../config'; export default function Login() { + const {providers, loading} = useAuthProviders(); + const handleSignIn = async (providerId: string) => { try { if (providerId === 'azure') { const authUrl = await getAuthorisationURLWithQueryParamsAndSetState({ thirdPartyId: 'azure', - frontendRedirectURI: 'http://localhost:3000/auth/callback/azure', + frontendRedirectURI: `${getApiBaseUrl()}/auth/callback/azure`, }); window.location.assign(authUrl); } else { @@ -28,6 +31,37 @@ export default function Login() { } }; + const getProviderLogo = (providerId: string) => { + switch (providerId) { + case 'github': + return githubLogo; + case 'azure': + return microsoftLogo; + default: + return null; + } + }; + + const NoProvidersMessage = () => ( +
+

+ No authentication providers configured. +

+

+ Please follow the{' '} + + OAuth setup guide + {' '} + to configure GitHub or Microsoft authentication. +

+
+ ); + return (
@@ -36,7 +70,7 @@ export default function Login() { Logo
@@ -46,112 +80,32 @@ export default function Login() {

- - + {loading ? ( +
+ +
+ ) : providers.length > 0 ? ( + providers.map((provider) => ( + + )) + ) : ( + + )}
- {/*
*/} - {/*
*/} - {/* */} - {/*
*/} - {/* /!*
*!/*/} - {/* /!* *!/*/} - {/* /!* OR*!/*/} - {/* /!* *!/*/} - {/* /!*
*!/*/} - {/*
*/} - {/*
*/} - {/*
*/} - {/* */} - {/*
*/} - {/*
*/} - {/*
*/} - {/* */} - {/* setShowPassword(!showPassword)}*/} - {/* >*/} - {/* {showPassword ? (*/} - {/* Hide password*/} - {/* ) : (*/} - {/* Show password*/} - {/* )}*/} - {/* */} - {/* {showPassword ? (*/} - {/* <>*/} - {/* */} - {/* */} - {/* */} - {/* ) : (*/} - {/* <>*/} - {/* */} - {/* */} - {/* */} - {/* )}*/} - {/* */} - {/* */} - {/*
*/} - {/*
*/} - {/* */} - {/* Forgot password?*/} - {/* */} - {/* */} - {/*
*/} - {/*
*/} - {/* Don't have an account?{' '}*/} - {/* */} - {/* Sign up*/} - {/* */} - {/*
*/} diff --git a/frontend/src/config.ts b/frontend/src/config.ts index 98a5601..7305754 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -1,34 +1,34 @@ -import ThirdParty, { Github } from "supertokens-auth-react/recipe/thirdparty"; -import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; -import Session from "supertokens-auth-react/recipe/session"; +import ThirdParty, {Github} from 'supertokens-auth-react/recipe/thirdparty'; +import {ThirdPartyPreBuiltUI} from 'supertokens-auth-react/recipe/thirdparty/prebuiltui'; +import Session from 'supertokens-auth-react/recipe/session'; -const BACKEND_PORT = 3033; -const FRONTEND_PORT = 3033; +export const apiDomain = `${process.env.REACT_APP_BACKEND_URL}:${process.env.REACT_APP_BACKEND_PORT}`; +const websiteDomain = `${process.env.REACT_APP_FRONTEND_URL}:${process.env.REACT_APP_FRONTEND_PORT}`; export const SuperTokensConfig = { appInfo: { - appName: "Instant Mock", - apiDomain: `http://localhost:${BACKEND_PORT}`, - websiteDomain: `http://localhost:${FRONTEND_PORT}`, - apiBasePath: "/auth", - websiteBasePath: "/auth" + appName: 'Instant Mock', + apiDomain, + websiteDomain, + apiBasePath: '/auth', + websiteBasePath: '/auth', }, recipeList: [ ThirdParty.init({ signInAndUpFeature: { - providers: [Github.init()] + providers: [Github.init()], }, }), - Session.init() - ] + Session.init(), + ], }; export const PreBuiltUIList = [ThirdPartyPreBuiltUI]; -export const ComponentWrapper = (props: { children: JSX.Element }): JSX.Element => { +export const ComponentWrapper = (props: { + children: JSX.Element; +}): JSX.Element => { return props.children; }; export const getApiBaseUrl = () => - process.env.NODE_ENV === 'development' - ? process.env.REACT_APP_API_BASE_URL || 'http://localhost:3033' - : ''; + process.env.NODE_ENV === 'development' ? websiteDomain : apiDomain; // we serve up the front end in a static bundle in prod diff --git a/frontend/src/hooks/useAuthProviders.ts b/frontend/src/hooks/useAuthProviders.ts new file mode 100644 index 0000000..9331793 --- /dev/null +++ b/frontend/src/hooks/useAuthProviders.ts @@ -0,0 +1,30 @@ +import { useState, useEffect } from 'react'; +import { getApiBaseUrl } from '../config'; + +interface AuthProvider { + id: string; + name: string; +} + +export function useAuthProviders() { + const [providers, setProviders] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchProviders = async () => { + try { + const response = await fetch(`${getApiBaseUrl()}/auth-providers`); + const data = await response.json(); + setProviders(data.providers); + } catch (error) { + console.error('Failed to fetch auth providers:', error); + } finally { + setLoading(false); + } + }; + + fetchProviders(); + }, []); + + return { providers, loading }; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3ca714a..7661295 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "instant-mock", - "version": "1.0.0", + "version": "1.0.0-beta.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "instant-mock", - "version": "1.0.0", + "version": "1.0.0-beta.2", "license": "ISC", "devDependencies": { "@types/jest": "^29.5.14", @@ -15,7 +15,6 @@ "cypress-iframe": "^1.0.1", "date-fns": "^4.1.0", "dotenv": "^16.4.5", - "eslint": "8.57.1", "globals": "^15.12.0", "ts-node": "^10.9.2", "typescript-eslint": "^8.13.0", diff --git a/package.json b/package.json index 9df92a7..e7ead96 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,13 @@ "main": "index.js", "module": "true", "scripts": { - "install:all": "npm install && npm --prefix frontend install && npm --prefix backend install", - "start": "npm run install:all && npm --prefix frontend run build && npm --prefix backend run build && NODE_ENV=development PORT=3032 npm --prefix backend start", - "db:connect": "docker exec -it supertokens_db psql -U supertokens_user -d supertokens", - "e2e:record": "ts-node scripts/run-e2e-tests.ts record", - "e2e:test": "ts-node scripts/run-e2e-tests.ts test", + "start": "docker compose up", + "start:build": "docker compose up --build", "cypress:basic": "concurrently -k -s first \"NODE_ENV=development PORT=3032 npm --prefix backend start\" \"wait-on http://localhost:3032 && CYPRESS_BASE_URL=http://localhost:3032 cypress run --spec 'cypress/e2e/basic-functionality.cy.ts'\"", "cypress:open": "concurrently --kill-others --success first --color always \"FORCE_COLOR=1 npm start\" \"wait-on http://localhost:3032 && FORCE_COLOR=1 CYPRESS_BASE_URL=http://localhost:3032 cypress open\"", - "dev": "docker-compose up", - "dev:build": "docker-compose up --build" + "db:auth": "docker exec -it supertokens_db psql -U supertokens_user -d supertokens", + "e2e:record": "ts-node scripts/run-e2e-tests.ts record", + "e2e:test": "ts-node scripts/run-e2e-tests.ts test" }, "keywords": [], "author": "", @@ -34,4 +32,4 @@ "node": "20", "npm": "10" } -} \ No newline at end of file +}