Skip to content

Commit

Permalink
Add prettier
Browse files Browse the repository at this point in the history
Moment of silence for the git blame usefulness
  • Loading branch information
PurkkaKoodari committed Aug 5, 2024
1 parent 8938cf7 commit 4371342
Show file tree
Hide file tree
Showing 230 changed files with 4,389 additions and 4,544 deletions.
6 changes: 2 additions & 4 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ module.exports = {
"extends": [
"airbnb",
"airbnb/hooks",
"airbnb-typescript"
"airbnb-typescript",
"prettier"
],
"plugins": [
"@typescript-eslint",
Expand All @@ -41,9 +42,6 @@ module.exports = {
"browser": true
},
"rules": {
"max-len": ["error", 120, 2],
"@typescript-eslint/semi": ["error", "always"],
"@typescript-eslint/quotes": ["error", "single"],
// To allow grouping of class members - especially for Models.
"@typescript-eslint/lines-between-class-members": "off",
// Doesn't increase code quality with redux.
Expand Down
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"printWidth": 100
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"start": "pnpm run -r --parallel start",
"lint": "eslint packages",
"lint:fix": "npm run lint -- --fix",
"format": "prettier -w packages/*/{src,test}",
"typecheck": "pnpm run -r --workspace-concurrency=1 typecheck",
"test": "pnpm run -r test",
"test:backend": "pnpm run --filter ilmomasiina-backend test"
Expand All @@ -31,13 +32,15 @@
"eslint": "^8.56.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^12.0.0",
"prettier": "^3.3.3",
"typescript": "~5.2.2"
},
"browserslist": {
Expand Down
94 changes: 47 additions & 47 deletions packages/ilmomasiina-backend/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import fastifyCompress from '@fastify/compress';
import fastifyCors from '@fastify/cors';
import fastifySensible from '@fastify/sensible';
import fastifyStatic from '@fastify/static';
import Ajv from 'ajv';
import ajvFormats from 'ajv-formats';
import fastify, { FastifyInstance } from 'fastify';
import cron from 'node-cron';
import path from 'path';
import zlib from 'zlib';

import AdminAuthSession from './authentication/adminAuthSession';
import config from './config';
import anonymizeOldSignups from './cron/anonymizeOldSignups';
import deleteOldAuditLogs from './cron/deleteOldAuditLogs';
import deleteUnconfirmedSignups from './cron/deleteUnconfirmedSignups';
import removeDeletedData from './cron/removeDeletedData';
import enforceHTTPS from './enforceHTTPS';
import setupDatabase from './models';
import setupRoutes from './routes';
import { isInitialSetupDone } from './routes/admin/users/createInitialUser';
import fastifyCompress from "@fastify/compress";
import fastifyCors from "@fastify/cors";
import fastifySensible from "@fastify/sensible";
import fastifyStatic from "@fastify/static";
import Ajv from "ajv";
import ajvFormats from "ajv-formats";
import fastify, { FastifyInstance } from "fastify";
import cron from "node-cron";
import path from "path";
import zlib from "zlib";

import AdminAuthSession from "./authentication/adminAuthSession";
import config from "./config";
import anonymizeOldSignups from "./cron/anonymizeOldSignups";
import deleteOldAuditLogs from "./cron/deleteOldAuditLogs";
import deleteUnconfirmedSignups from "./cron/deleteUnconfirmedSignups";
import removeDeletedData from "./cron/removeDeletedData";
import enforceHTTPS from "./enforceHTTPS";
import setupDatabase from "./models";
import setupRoutes from "./routes";
import { isInitialSetupDone } from "./routes/admin/users/createInitialUser";

// Disable type coercion for request bodies - we don't need it, and it breaks stuff like anyOf
const bodyCompiler = new Ajv({
Expand All @@ -31,7 +31,7 @@ const bodyCompiler = new Ajv({
ajvFormats(bodyCompiler);

const defaultCompiler = new Ajv({
coerceTypes: 'array',
coerceTypes: "array",
useDefaults: true,
removeAdditional: true,
addUsedSchema: false,
Expand All @@ -44,47 +44,47 @@ export default async function initApp(): Promise<FastifyInstance> {

const server = fastify({
trustProxy: config.isAzure || config.trustProxy, // Get IPs from X-Forwarded-For
logger: !['test', 'bench'].some((env) => env === config.nodeEnv), // Enable logger when not testing or benchmarking
logger: !["test", "bench"].some((env) => env === config.nodeEnv), // Enable logger when not testing or benchmarking
});
server.setValidatorCompiler(({ httpPart, schema }) => (
httpPart === 'body' ? bodyCompiler.compile(schema) : defaultCompiler.compile(schema)
));
server.setValidatorCompiler(({ httpPart, schema }) =>
httpPart === "body" ? bodyCompiler.compile(schema) : defaultCompiler.compile(schema),
);

// Enable admin registration if no users are present.
// The "cached" flag is present to prevent an unnecessary DB check on every /api/events call.
server.decorate('initialSetupDone', await isInitialSetupDone());
server.decorate("initialSetupDone", await isInitialSetupDone());

// Register fastify-sensible (https://github.com/fastify/fastify-sensible)
server.register(fastifySensible);

// Enable configurable CORS
if (config.allowOrigin) {
const corsOrigins = config.allowOrigin === '*' ? '*' : (config.allowOrigin?.split(',') ?? []);
const corsOrigins = config.allowOrigin === "*" ? "*" : (config.allowOrigin?.split(",") ?? []);
await server.register(fastifyCors, {
origin: corsOrigins,
});
}

// Announce Ilmomasiina version as header
if (config.version) {
server.addHook('onRequest', async (_, reply) => {
reply.header('X-Ilmomasiina-Version', config.version);
server.addHook("onRequest", async (_, reply) => {
reply.header("X-Ilmomasiina-Version", config.version);
});
}

// Enforce HTTPS connections in production
if (config.nodeEnv === 'production') {
if (config.nodeEnv === "production") {
if (config.enforceHttps) {
server.addHook('onRequest', enforceHTTPS(config));
server.addHook("onRequest", enforceHTTPS(config));
console.info(
'Enforcing HTTPS connections.\n'
+ 'Ensure your load balancer or reverse proxy sets X-Forwarded-Proto (or X-ARR-SSL in Azure).',
"Enforcing HTTPS connections.\n" +
"Ensure your load balancer or reverse proxy sets X-Forwarded-Proto (or X-ARR-SSL in Azure).",
);
} else {
console.warn(
'HTTPS connections are not enforced by Ilmomasiina.\n'
+ 'For security reasons, please set ENFORCE_HTTPS=proxy and configure your load balancer or reverse proxy to '
+ 'forward only HTTPS connections to Ilmomasiina.',
"HTTPS connections are not enforced by Ilmomasiina.\n" +
"For security reasons, please set ENFORCE_HTTPS=proxy and configure your load balancer or reverse proxy to " +
"forward only HTTPS connections to Ilmomasiina.",
);
}
}
Expand All @@ -103,12 +103,12 @@ export default async function initApp(): Promise<FastifyInstance> {
setHeaders: (res, filePath) => {
// set immutable cache for javascript files with hash in the name
if (javascriptHashRegex.test(filePath)) {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
}
},
});
server.get('*', (_req, reply) => {
reply.sendFile('index.html');
server.get("*", (_req, reply) => {
reply.sendFile("index.html");
});
}
// Add on-the-fly compression
Expand All @@ -122,28 +122,28 @@ export default async function initApp(): Promise<FastifyInstance> {
});

server.register(setupRoutes, {
prefix: '/api',
prefix: "/api",
adminSession: new AdminAuthSession(config.feathersAuthSecret),
});

if (config.nodeEnv !== 'test') {
if (config.nodeEnv !== "test") {
// Every minute, remove signups that haven't been confirmed fast enough
cron.schedule('* * * * *', deleteUnconfirmedSignups);
cron.schedule("* * * * *", deleteUnconfirmedSignups);

// Daily at 8am, anonymize old signups
cron.schedule('0 8 * * *', anonymizeOldSignups);
cron.schedule("0 8 * * *", anonymizeOldSignups);

// Daily at 8am, delete deleted items from the database
cron.schedule('0 8 * * *', removeDeletedData);
cron.schedule("0 8 * * *", removeDeletedData);

// Daily at 8am, delete old audit logs
cron.schedule('0 8 * * *', deleteOldAuditLogs);
cron.schedule("0 8 * * *", deleteOldAuditLogs);
}

return server;
}

declare module 'fastify' {
declare module "fastify" {
interface FastifyInstance {
/** If set to false, GET /api/events raises an error.
* This is "cached" in the application instance to avoid an unnecessary database query.
Expand Down
62 changes: 35 additions & 27 deletions packages/ilmomasiina-backend/src/auditlog/index.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,45 @@
import { FastifyInstance } from 'fastify';
import { Transaction } from 'sequelize';
import { FastifyInstance } from "fastify";
import { Transaction } from "sequelize";

import type { AuditEvent } from '@tietokilta/ilmomasiina-models';
import { AuditLog } from '../models/auditlog';
import type { Event } from '../models/event';
import type { Signup } from '../models/signup';
import type { AuditEvent } from "@tietokilta/ilmomasiina-models";
import { AuditLog } from "../models/auditlog";
import type { Event } from "../models/event";
import type { Signup } from "../models/signup";

/**
* Creates an {@link AuditLogger}
*
* @param ipAddress related ip address
* @param user a function returning username (email), executed when events are logged
*/
function eventLogger(ipAddress: string, user?: () => (string | null)) {
function eventLogger(ipAddress: string, user?: () => string | null) {
return async (
action: AuditEvent,
{
transaction, event, signup, extra,
transaction,
event,
signup,
extra,
}: {
event?: Pick<Event, 'id' | 'title'>,
signup?: Signup,
transaction?: Transaction,
extra?: object,
event?: Pick<Event, "id" | "title">;
signup?: Signup;
transaction?: Transaction;
extra?: object;
},
) => {
await AuditLog.create({
user: user ? user() : null,
action,
eventId: event?.id || signup?.quota?.event?.id || null,
eventName: event?.title || signup?.quota?.event?.title || null,
signupId: signup?.id || null,
signupName: signup ? `${signup.firstName} ${signup.lastName}` : null,
extra: extra ? JSON.stringify(extra) : null,
ipAddress,
}, { transaction });
await AuditLog.create(
{
user: user ? user() : null,
action,
eventId: event?.id || signup?.quota?.event?.id || null,
eventName: event?.title || signup?.quota?.event?.title || null,
signupId: signup?.id || null,
signupName: signup ? `${signup.firstName} ${signup.lastName}` : null,
extra: extra ? JSON.stringify(extra) : null,
ipAddress,
},
{ transaction },
);
};
}

Expand All @@ -44,19 +50,21 @@ function eventLogger(ipAddress: string, user?: () => (string | null)) {
* Using this method, user and ip address information will be automatically inferred into audit log event.
*/
export function addLogEventHook(fastify: FastifyInstance): void {
fastify.decorateRequest('logEvent', () => { throw new Error('Not initialized'); });
fastify.addHook('onRequest', async (req) => {
fastify.decorateRequest("logEvent", () => {
throw new Error("Not initialized");
});
fastify.addHook("onRequest", async (req) => {
(req.logEvent as AuditLogger) = eventLogger(req.ip, () => req.sessionData?.email || null);
});
}

/** Use to log internally triggered actions to the audit log (actions run by cron jobs for example) */
export const internalAuditLogger = eventLogger('internal');
export const internalAuditLogger = eventLogger("internal");

export type AuditLogger = ReturnType<typeof eventLogger>;

declare module 'fastify' {
declare module "fastify" {
interface FastifyRequest {
readonly logEvent: AuditLogger,
readonly logEvent: AuditLogger;
}
}
35 changes: 20 additions & 15 deletions packages/ilmomasiina-backend/src/authentication/adminAuthSession.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
import {
createSigner, createVerifier, SignerSync, VerifierSync,
} from 'fast-jwt';
import { FastifyRequest } from 'fastify';
import { createSigner, createVerifier, SignerSync, VerifierSync } from "fast-jwt";
import { FastifyRequest } from "fastify";

import type { UserID, UserSchema } from '@tietokilta/ilmomasiina-models';
import config from '../config';
import { BadSession } from './errors';
import type { UserID, UserSchema } from "@tietokilta/ilmomasiina-models";
import config from "../config";
import { BadSession } from "./errors";

export interface AdminTokenData {
user: UserID;
email: UserSchema['email'];
email: UserSchema["email"];
}

export default class AdminAuthSession {
/** Session lifetime in seconds */
static TTL = config.nodeEnv === 'development' ? 365 * 24 * 60 * 60 : 10 * 60;
static TTL = config.nodeEnv === "development" ? 365 * 24 * 60 * 60 : 10 * 60;

private readonly sign: typeof SignerSync;
private readonly verify: typeof VerifierSync;

constructor(secret: string) {
this.sign = createSigner({ key: secret, expiresIn: AdminAuthSession.TTL * 1000 });
this.verify = createVerifier({ key: secret, maxAge: AdminAuthSession.TTL * 1000 });
this.sign = createSigner({
key: secret,
expiresIn: AdminAuthSession.TTL * 1000,
});
this.verify = createVerifier({
key: secret,
maxAge: AdminAuthSession.TTL * 1000,
});
}

/**
Expand All @@ -43,16 +47,17 @@ export default class AdminAuthSession {
const header = request.headers.authorization; // Yes, Fastify converts header names to lowercase :D

if (!header) {
throw new BadSession('Missing Authorization header');
throw new BadSession("Missing Authorization header");
}

const token = Array.isArray(header) ? header[0] : header;

try { // Try to verify token
try {
// Try to verify token
const data = this.verify(token);
return { user: parseInt(data.user), email: data.email || '' };
return { user: parseInt(data.user), email: data.email || "" };
} catch {
throw new BadSession('Invalid session');
throw new BadSession("Invalid session");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import bcrypt from 'bcrypt';
import { BadRequest } from 'http-errors';
import bcrypt from "bcrypt";
import { BadRequest } from "http-errors";

export default class AdminPasswordAuth {
static validateNewPassword(password: string): void {
if (password.length < 10) {
throw new BadRequest('Password must be at least 10 characters long');
throw new BadRequest("Password must be at least 10 characters long");
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/ilmomasiina-backend/src/authentication/errors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ErrorCode } from '@tietokilta/ilmomasiina-models';
import CustomError from '../util/customError';
import { ErrorCode } from "@tietokilta/ilmomasiina-models";
import CustomError from "../util/customError";

// eslint-disable-next-line import/prefer-default-export
export class BadSession extends CustomError {
Expand Down
Loading

0 comments on commit 4371342

Please sign in to comment.