Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions graphql/server/src/middleware/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getNodeEnv } from '@constructive-io/graphql-env';
import { Logger } from '@pgpmjs/logger';
import { svcCache } from '@pgpmjs/server-utils';
import { PgpmOptions } from '@pgpmjs/types';
import { NextFunction, Request, Response } from 'express';
Expand All @@ -16,6 +17,9 @@ import { ApiStructure, Domain, SchemaNode, Service, Site } from '../types';
import { ApiByNameQuery, ApiQuery, ListOfAllDomainsOfDb } from './gql';
import './types'; // for Request type

const log = new Logger('api');
const isDev = () => getNodeEnv() === 'development';

const transformServiceToApi = (svc: Service): ApiStructure => {
const api = svc.data.api;
const schemaNames =
Expand Down Expand Up @@ -118,6 +122,10 @@ export const createApiMiddleware = (opts: any) => {
const api = transformServiceToApi(svc);
req.api = api;
req.databaseId = api.databaseId;
if (isDev())
log.debug(
`Resolved API: db=${api.dbname}, schemas=[${api.schema?.join(', ')}]`
);
next();
} catch (e: any) {
if (e.code === 'NO_VALID_SCHEMAS') {
Expand All @@ -131,7 +139,7 @@ export const createApiMiddleware = (opts: any) => {
)
);
} else {
console.error(e);
log.error('API middleware error:', e);
res.status(500).send(errorPage50x);
}
}
Expand Down Expand Up @@ -223,7 +231,7 @@ const queryServiceByDomainAndSubdomain = async ({
});

if (result.errors?.length) {
console.error(result.errors);
log.error('GraphQL query errors:', result.errors);
return null;
}

Expand Down Expand Up @@ -260,7 +268,7 @@ const queryServiceByApiName = async ({
});

if (result.errors?.length) {
console.error(result.errors);
log.error('GraphQL query errors:', result.errors);
return null;
}

Expand Down Expand Up @@ -323,14 +331,18 @@ export const getApiConfig = async (

let svc;
if (svcCache.has(key)) {
if (isDev()) log.debug(`Cache HIT for key=${key}`);
svc = svcCache.get(key);
} else {
if (isDev()) log.debug(`Cache MISS for key=${key}, looking up API`);
const apiOpts = (opts as any).api || {};
const allSchemata = apiOpts.metaSchemas || [];
const validatedSchemata = await validateSchemata(rootPgPool, allSchemata);

if (validatedSchemata.length === 0) {
const message = `No valid schemas found for domain: ${domain}, subdomain: ${subdomain}`;
const apiOpts2 = (opts as any).api || {};
const message = `No valid schemas found. Configured metaSchemas: [${(apiOpts2.metaSchemas || []).join(', ')}]`;
if (isDev()) log.debug(message);
const error: any = new Error(message);
error.code = 'NO_VALID_SCHEMAS';
throw error;
Expand Down
12 changes: 11 additions & 1 deletion graphql/server/src/middleware/auth.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { getNodeEnv } from '@constructive-io/graphql-env';
import { Logger } from '@pgpmjs/logger';
import { PgpmOptions } from '@pgpmjs/types';
import { NextFunction, Request, RequestHandler, Response } from 'express';
import { getPgPool } from 'pg-cache';
import pgQueryContext from 'pg-query-context';
import './types'; // for Request type

const log = new Logger('auth');
const isDev = () => getNodeEnv() === 'development';

export const createAuthenticateMiddleware = (
opts: PgpmOptions
): RequestHandler => {
Expand All @@ -24,7 +29,10 @@ export const createAuthenticateMiddleware = (
});
const rlsModule = api.rlsModule;

if (!rlsModule) return next();
if (!rlsModule) {
if (isDev()) log.debug('No RLS module configured, skipping auth');
return next();
}

const authFn = opts.server.strictAuth
? rlsModule.authenticateStrict
Expand Down Expand Up @@ -63,7 +71,9 @@ export const createAuthenticateMiddleware = (
}

token = result.rows[0];
if (isDev()) log.debug(`Auth success: role=${token.role}`);
} catch (e: any) {
log.error('Auth error:', e.message);
res.status(200).json({
errors: [
{
Expand Down
28 changes: 20 additions & 8 deletions graphql/server/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getEnvOptions } from '@constructive-io/graphql-env';
import { getEnvOptions, getNodeEnv } from '@constructive-io/graphql-env';
import { Logger } from '@pgpmjs/logger';
import { healthz, poweredBy, trustProxy } from '@pgpmjs/server-utils';
import { PgpmOptions } from '@pgpmjs/types';
Expand All @@ -17,6 +17,7 @@ import { flush, flushService } from './middleware/flush';
import { graphile } from './middleware/graphile';

const log = new Logger('server');
const isDev = () => getNodeEnv() === 'development';

export const GraphQLServer = (rawOpts: PgpmOptions = {}) => {
const envOptions = getEnvOptions(rawOpts);
Expand All @@ -36,18 +37,32 @@ class Server {
const api = createApiMiddleware(opts);
const authenticate = createAuthenticateMiddleware(opts);

// Log startup config in dev mode
if (isDev()) {
log.debug(
`Database: ${opts.pg?.database}@${opts.pg?.host}:${opts.pg?.port}`
);
log.debug(
`Meta schemas: ${(opts as any).api?.metaSchemas?.join(', ') || 'default'}`
);
}

healthz(app);
trustProxy(app, opts.server.trustProxy);
// Warn if a global CORS override is set in production
const fallbackOrigin = opts.server?.origin?.trim();
if (fallbackOrigin && process.env.NODE_ENV === 'production') {
if (fallbackOrigin === '*') {
log.warn('CORS wildcard ("*") is enabled in production; this effectively disables CORS and is not recommended. Prefer per-API CORS via meta schema.');
log.warn(
'CORS wildcard ("*") is enabled in production; this effectively disables CORS and is not recommended. Prefer per-API CORS via meta schema.'
);
} else {
log.warn(`CORS override origin set to ${fallbackOrigin} in production. Prefer per-API CORS via meta schema.`);
log.warn(
`CORS override origin set to ${fallbackOrigin} in production. Prefer per-API CORS via meta schema.`
);
}
}

app.use(poweredBy('constructive'));
app.use(cors(fallbackOrigin));
app.use(graphqlUpload.graphqlUploadExpress());
Expand All @@ -69,10 +84,7 @@ class Server {

httpServer.on('error', (err: NodeJS.ErrnoException) => {
if (err.code === 'EADDRINUSE') {
this.error(
`Port ${server?.port ?? 'unknown'} is already in use`,
err
);
this.error(`Port ${server?.port ?? 'unknown'} is already in use`, err);
} else {
this.error('Server failed to start', err);
}
Expand Down
8 changes: 6 additions & 2 deletions graphql/types/src/graphile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const graphileDefaults: GraphileOptions = {
export const graphileFeatureDefaults: GraphileFeatureOptions = {
simpleInflection: true,
oppositeBaseNames: true,
postgis: true
postgis: true,
};

/**
Expand All @@ -76,5 +76,9 @@ export const apiDefaults: ApiOptions = {
roleName: 'administrator',
defaultDatabaseId: 'hard-coded',
isPublic: true,
metaSchemas: ['collections_public', 'meta_public']
metaSchemas: [
'services_public',
'metaschema_public',
'metaschema_modules_public',
],
};