Skip to content

Commit

Permalink
Feat 7/harden api (#17)
Browse files Browse the repository at this point in the history
* feat: first pass at hardening api
* ci: initial
* fix: path to lock file
* fix: sigh tis late
* fix: sigh tis late
* fix: sigh tis late
* fix: sigh tis late
* fix: almost there
* fix: almost there
* fix: dass how da cookie crumbles
* chore: check env vars at startup
  • Loading branch information
minademian committed Jan 12, 2024
1 parent a5b4ddc commit d260e31
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 14 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI
on:
push:
branches:
# Change this if your primary branch is not main
- main
pull_request:

jobs:
main:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20]
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm i --frozen-lockfile
- uses: nrwl/nx-set-shas@v3
# This line is needed for nx affected to work when CI is running on a PR
- run: git fetch
- run: git branch --track main origin/main
- run: npx nx affected -t build --parallel=3
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,6 @@ client.json
token.json
credentials.json
data*.json

# Redis
dump.rdb
47 changes: 45 additions & 2 deletions apps/api/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,64 @@
import express from 'express';
import cors from 'cors';
import bodyParser from 'body-parser';

import session from 'express-session';
import helmet from 'helmet';
import cors from 'cors';
import csrf from 'csurf';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
import { rateLimit } from 'express-rate-limit';
import hpp from 'hpp';

import { routes } from './routes';
import config from './services/config';
import { googleAuth } from './middlewares/google.middleware';

const redisClient = createClient();
redisClient.connect().catch(console.error);

const redisStore = new RedisStore({
client: redisClient,
prefix: config.ApiStorePrefix,
});

const app = express();

app.use(bodyParser.json());
const limiter = rateLimit({
windowMs: 60 * 60 * 1000,
limit: 100,
standardHeaders: 'draft-7', // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header
legacyHeaders: false,
// TODO: refactor with a lib that's compatible with both limiter and express
});

config.checkEnvVariables();

app.use(
bodyParser.json({
limit: '8mb',
})
);

app.use(
session({
store: redisStore,
resave: false,
saveUninitialized: false,
secret: config.ApiStorePrefix,
})
);
app.use(hpp());
app.disable('x-powered-by');
app.use(helmet());
app.use(csrf());
app.use(
cors({
credentials: true,
origin: [config.appUrl],
})
);
app.use(limiter);

app.use(`/${config.apiPrefix}`, googleAuth, routes);

Expand Down
18 changes: 10 additions & 8 deletions apps/api/src/services/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@ const ENV_VARS = [
'GOOGLE_CLIENT_ID',
'GOOGLE_CLIENT_SECRET',
'GOOGLE_SPREADSHEET_ID',
'APP_ID',
'APP_SECRET',
'VERIFY_TOKEN',
'APP_URL',
'APP_PORT',
'SESSION_SECRET',
'API_STORE_PREFIX',
];

type Origin = string | boolean | RegExp;
type EnvVar = string | undefined;
type Port = string | number | undefined;
type EnvVarsFn = () => void;

type Config = {
GoogleSpreadsheetId: EnvVar;
Expand All @@ -33,7 +32,9 @@ type Config = {
apiUrl: EnvVar;
GoogleApiUrl: Origin;
redirectUri: EnvVar;
checkEnvVariables: Function;
checkEnvVariables: EnvVarsFn;
SessionSecret: EnvVar;
ApiStorePrefix: EnvVar;
};

const config: Config = {
Expand All @@ -49,7 +50,8 @@ const config: Config = {
GoogleClientId: process.env.GOOGLE_CLIENT_ID,
redirectUri: process.env.GOOGLE_REDIRECT_URI,
port: process.env.PORT || 8000,

SessionSecret: process.env.SESSION_SECRET,
ApiStorePrefix: process.env.API_STORE_PREFIX,
get appUrl(): Origin {
return `${this.host}:${this.appPort}`;
},
Expand All @@ -59,12 +61,12 @@ const config: Config = {
get GoogleApiUrl(): Origin {
return `${this.GoogleApiDomain}`;
},
checkEnvVariables: function () {
checkEnvVariables(): void {
ENV_VARS.forEach(function (key) {
if (!process.env[key]) {
console.warn('WARNING: Missing the environment variable ' + key);
} else {
if (['APP_URL', 'SHOP_URL'].includes(key)) {
if (['APP_URL'].includes(key)) {
const url: string = process.env[key] as string;
if (!url.startsWith('https://')) {
console.warn(
Expand Down
17 changes: 17 additions & 0 deletions apps/api/src/services/redis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import RedisStore from 'connect-redis';
import { createClient } from 'redis';

export const SessionStore = async () => {
const redisClient = createClient();
redisClient.connect().catch(console.error);

const redisStore = new RedisStore({
client: redisClient,
prefix: 'myapp:',
});

return new Promise((resolve, reject) => {
if (!redisStore) reject(null);
resolve(redisStore);
});
};
3 changes: 2 additions & 1 deletion apps/api/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"module": "ESNext",
"target": "ES2017",
"types": ["node", "express"]
},
"exclude": [
Expand Down
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,22 @@
"@swc/helpers": "~0.5.2",
"axios": "^1.0.0",
"body-parser": "^1.20.2",
"connect-redis": "^7.1.0",
"cors": "^2.8.5",
"csurf": "^1.11.0",
"dotenv": "^16.3.1",
"express": "^4.18.1",
"express-rate-limit": "^7.1.5",
"express-session": "^1.17.3",
"google-auth-library": "^9.4.2",
"googleapis": "^130.0.0",
"helmet": "^7.1.0",
"hpp": "^0.2.3",
"nock": "^13.4.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "18.2.0",
"redis": "^4.6.12",
"styled-components": "5.3.6",
"supertest": "^6.3.3",
"tldts": "^6.1.2",
Expand Down
Loading

0 comments on commit d260e31

Please sign in to comment.