Skip to content

Commit

Permalink
chore: finalize no-auth vs auth experiences, conditional login dropdo…
Browse files Browse the repository at this point in the history
…wn component, tuning logs
  • Loading branch information
samjcombs committed Nov 22, 2024
1 parent 28608d6 commit 207354e
Show file tree
Hide file tree
Showing 14 changed files with 222 additions and 107 deletions.
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ 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
ARG REACT_APP_FRONTEND_REQUIRE_AUTH
ENV REACT_APP_FRONTEND_REQUIRE_AUTH=$REACT_APP_FRONTEND_REQUIRE_AUTH

COPY frontend/package*.json ./
RUN npm ci
Expand All @@ -25,12 +27,14 @@ RUN npm ci

COPY backend/ .
RUN npm run build
RUN cp package.json dist/


FROM node:20-alpine AS final
WORKDIR /app

COPY --from=backend-builder /app/backend/dist ./backend/dist
COPY --from=backend-builder /app/backend/package.json ./backend/
COPY --from=backend-builder /app/backend/node_modules ./backend/node_modules
COPY --from=frontend-builder /app/frontend/build ./frontend/build

Expand Down
4 changes: 2 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"version": "1.0.0-beta.2",
"main": "dist/server.js",
"scripts": {
"build": "npx tsc && npm run copy-graphql",
"build": "rm -rf dist/ && npx tsc && npm run copy-graphql",
"codegen:apollo": "graphql-codegen --config codegen.apollo.ts",
"copy-graphql": "cp -R src/**/*.graphql dist/graphql/",
"copy-graphql": "cp src/graphql/*.graphql dist/graphql/",
"db:reset": "ts-node src/scripts/reset-database.ts",
"debug": "kill -9 $(lsof -t -i :9229) 2>/dev/null || true && DEBUGGING=true node --inspect-brk=9229 -r ts-node/register src/server.ts & pid=$! && sleep 2 && open -a 'Google Chrome' 'chrome://inspect' && wait $pid",
"dev": "npm run db:reset && ts-node-dev --respawn --transpile-only src/server.ts",
Expand Down
5 changes: 5 additions & 0 deletions backend/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ const config = convict({
default: NodeEnvironment.Development,
},
},
requireAuth: {
env: 'BACKEND_REQUIRE_AUTH',
format: Boolean,
default: false,
},
server: {
port: {
env: 'PORT',
Expand Down
4 changes: 3 additions & 1 deletion backend/src/graphql/example.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ input ProductInput {
}

type Query {
id: ID
product(id: ID!): Product
products(filter: ProductFilter, first: Int, skip: Int): [Product]
}
Expand All @@ -61,4 +62,5 @@ type User {
username: String!
email: String!
reviews: [Review]
}
}

26 changes: 6 additions & 20 deletions backend/src/middleware/auth.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
import {verifySession} from 'supertokens-node/recipe/session/framework/express';
import {middleware, errorHandler} from 'supertokens-node/framework/express';
import config from '../config/config';

const sessionConfig = {
sessionRequired: config.get('requireAuth'),
};

export const authMiddleware = {
verify: verifySession({
sessionRequired: false,
}),
verify: verifySession(sessionConfig),
init: middleware(),
error: errorHandler(),
};

// export interface AuthRequest extends Request {
// session: {
// getUserId(): string;
// };
// }
//
// export const requireAuth = (
// req: AuthRequest,
// res: express.Response,
// next: express.NextFunction
// ) => {
// if (!req.session) {
// return res.status(401).json({error: 'Unauthorized'});
// }
// next();
// };
16 changes: 9 additions & 7 deletions backend/src/routes/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,15 @@ const handleGraphQLRequest = async (
return res.status(400).json({message: 'Seed group not found'});
}

logger.graph('Handling GraphQL Request:', {
graphId,
variantName,
operationName,
// query,
// variables,
});
if (operationName !== 'IntrospectionQuery') {
logger.graph('Handling GraphQL Request:', {
graphId,
variantName,
operationName,
// query,
// variables,
});
}

if (!operationName) {
return res
Expand Down
17 changes: 16 additions & 1 deletion backend/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,18 @@ import proposalsRoutes from './routes/proposals';
import seedGroupsRoutes from './routes/seedGroups';
import seedsRoutes from './routes/seeds';
import {logger} from './utilities/logger';
import {version as APP_VERSION} from '../package.json';
import fs from 'fs';

const isTypescript = __filename.endsWith('.ts');
const ProxyAgent = Undici.ProxyAgent;
const setGlobalDispatcher = Undici.setGlobalDispatcher;

const displayBanner = () => {
if (process.env.NODE_ENV === 'production') {
logger.startup(`🚀 Instant Mock v${APP_VERSION}`);
return;
}

const banner = figlet.textSync('Instant Mock', {
font: 'Standard',
horizontalLayout: 'default',
Expand Down Expand Up @@ -184,6 +189,16 @@ const initializeApp = async () => {
});
};

let APP_VERSION = '0.0.0';
try {
const packageJson = JSON.parse(
fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')
);
APP_VERSION = packageJson.version;
} catch (error) {
console.warn('Failed to load package.json version:', error);
}

initializeApp().catch((error) => {
logger.error('Failed to start the application', error);
});
11 changes: 6 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,23 @@ services:
dockerfile: Dockerfile
args:
REACT_APP_BACKEND_URL: "${BACKEND_URL:-http://localhost}"
REACT_APP_BACKEND_PORT: "${PORT:-3033}"
REACT_APP_BACKEND_PORT: "${BACKEND_PORT:-3033}"
REACT_APP_FRONTEND_URL: "${FRONTEND_URL:-http://localhost}"
REACT_APP_FRONTEND_PORT: "${FRONTEND_PORT:-3033}"
REACT_APP_FRONTEND_REQUIRE_AUTH: true
container_name: instant-mock
environment:
PORT: "${PORT:-3033}"
APOLLO_API_KEY: "${APOLLO_API_KEY}"
NODE_ENV: "development"
REQUIRE_AUTH: true
PORT: "${BACKEND_PORT:-3033}"
NODE_ENV: "production"
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:-3033}:${PORT:-3033}"
- "${BACKEND_PORT:-3033}:${BACKEND_PORT:-3033}"
volumes:
- ./backend/data:/app/backend/data
networks:
Expand Down
21 changes: 18 additions & 3 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import {BrowserRouter as Router, Route, Routes} from 'react-router-dom';
import SuperTokens from 'supertokens-auth-react';
import SuperTokens, {SuperTokensWrapper} from 'supertokens-auth-react';
import CallbackHandler from './CallbackHandler';
import Home from './components/ui/home';
import Login from './components/ui/login';
import NotFound from './components/ui/not-found';
import SettingsPage from './components/ui/settings';
import {SuperTokensConfig} from './config/auth';
import {config} from './config/config';

SuperTokens.init(SuperTokensConfig);
if (config.requireAuth) {
SuperTokens.init(SuperTokensConfig);
}

function App() {
function AppRoutes() {
return (
<Router>
<Routes>
Expand All @@ -26,4 +29,16 @@ function App() {
);
}

function App() {
if (config.requireAuth) {
return (
<SuperTokensWrapper>
<AppRoutes />
</SuperTokensWrapper>
);
}

return <AppRoutes />;
}

export default App;
106 changes: 106 additions & 0 deletions frontend/src/components/ui/conditional-login-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {config, getApiBaseUrl} from '../../config/config';
import {LogIn, LogOut} from 'lucide-react';
import {Avatar, AvatarFallback, AvatarImage} from './avatar';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from './dropdown-menu';
import {useNavigate} from 'react-router';
import {SuperTokensWrapper} from 'supertokens-auth-react';
import Session, {
useSessionContext,
} from 'supertokens-auth-react/recipe/session';
import {useEffect, useState} from 'react';

const LoginDropdownWithAuthRequired = () => {
const [avatarUrl, setAvatarUrl] = useState<string>('/anonymous-avatar.svg');
const navigate = useNavigate();

async function handleSignOut() {
await Session.signOut();
navigate('/auth');
}

useEffect(() => {
const fetchAvatar = async () => {
try {
const response = await fetch(`${getApiBaseUrl()}/api/avatar`, {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
});

if (!response.ok) {
return;
}

const data = await response.json();
if (data.avatarUrl) {
setAvatarUrl(data.avatarUrl);
}
} catch (err) {
console.error('Error fetching avatar:', err);
}
};

fetchAvatar();
}, []);

const sessionContext = useSessionContext();
if (sessionContext.loading === true) return null;

const isLoggedIn = !!sessionContext.userId;

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Avatar className="cursor-pointer">
<AvatarImage src={avatarUrl} />
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
{isLoggedIn ? (
<DropdownMenuItem onSelect={handleSignOut}>
<LogOut className="mr-2 h-4 w-4" />
<span>Logout</span>
</DropdownMenuItem>
) : (
<DropdownMenuItem onSelect={() => navigate('/auth')}>
<LogIn className="mr-2 h-4 w-4" />
<span>Login</span>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
);
};

const LoginDropdownDisabled = () => {
const [avatarUrl] = useState<string>('/anonymous-avatar.svg');

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Avatar className="cursor-pointer">
<AvatarImage src={avatarUrl} />
</Avatar>
</DropdownMenuTrigger>
</DropdownMenu>
);
};

const ConditionalLoginDropdown = () => {
return config.requireAuth ? (
<SuperTokensWrapper>
<LoginDropdownWithAuthRequired />
</SuperTokensWrapper>
) : (
<LoginDropdownDisabled />
);
};

export default ConditionalLoginDropdown;
Loading

0 comments on commit 207354e

Please sign in to comment.