link-stack/packages/metamigo-config/index.ts

377 lines
10 KiB
TypeScript
Raw Normal View History

2023-02-13 12:41:30 +00:00
import * as process from "process";
import * as convict from "convict";
import * as Metamigo from "@digiresilience/metamigo-common";
2023-02-13 12:41:30 +00:00
import { defState } from "@digiresilience/montar";
export const configSchema = {
db: {
connection: {
doc: "The postgres connection url.",
format: "uri",
default: "postgresql://metamigo:metamigo@127.0.0.1:5435/metamigo_dev",
env: "DATABASE_URL",
sensitive: true,
},
name: {
doc: "The name of the postgres database",
format: String,
default: "metamigo_dev",
env: "DATABASE_NAME",
},
owner: {
doc: "The username of the postgres database owner",
format: String,
default: "metamigo",
env: "DATABASE_OWNER",
},
},
worker: {
connection: {
doc: "The postgres connection url for the worker database.",
format: "uri",
default: "postgresql://metamigo:metamigo@127.0.0.1:5435/metamigo_dev",
env: "WORKER_DATABASE_URL",
},
concurrency: {
doc: "The number of jobs to run concurrently",
default: 1,
format: "positiveInt",
env: "WORKER_CONCURRENT_JOBS",
},
pollInterval: {
doc: "How long to wait between polling for jobs in milliseconds (for jobs scheduled in the future/retries)",
default: 2000,
format: "positiveInt",
env: "WORKER_POLL_INTERVAL_MS",
},
},
postgraphile: {
auth: {
doc: "The postgres role that postgraphile logs in with",
format: String,
default: "metamigo_graphile_auth",
env: "DATABASE_AUTHENTICATOR",
},
appRootConnection: {
doc: "The postgres root/superuser connection url for development mode so PG can watch the schema changes, this is strangely named in the postgraphile API 'ownerConnectionString'",
format: String,
default: "postgresql://postgres:metamigo@127.0.0.1:5435/metamigo_dev",
env: "APP_ROOT_DATABASE_URL",
},
authConnection: {
doc: "The postgres connection URL for postgraphile, must not be superuser and must have limited privs.",
format: String,
default:
"postgresql://metamigo_graphile_auth:metamigo@127.0.0.1:5435/metamigo_dev",
env: "DATABASE_AUTH_URL",
},
visitor: {
doc: "The postgres role that postgraphile switches to",
format: String,
default: "app_postgraphile",
env: "DATABASE_VISITOR",
},
schema: {
doc: "The schema postgraphile should expose with graphql",
format: String,
default: "app_public",
},
enableGraphiql: {
doc: "Whether to enable the graphiql web interface or not",
format: "Boolean",
default: false,
env: "ENABLE_GRAPHIQL",
},
},
dev: {
shadowConnection: {
doc: "The shadow databse connection url used by postgraphile-migrate. Not needed in production.",
format: "uri",
default: "postgresql://metamigo:metamigo@127.0.0.1:5435/metamigo_shadow",
env: "SHADOW_DATABASE_URL",
sensitive: true,
},
rootConnection: {
doc: "The postgres root/superuser connection url for testing only, database must NOT be the app database. Not needed in production.",
format: "uri",
default: "postgresql://postgres:metamigo@127.0.0.1:5435/template1",
env: "ROOT_DATABASE_URL",
sensitive: true,
},
},
frontend: {
url: {
doc: "The url the frontend can be accessed at",
format: "url",
default: "http://localhost:3000",
env: "FRONTEND_URL",
},
apiUrl: {
doc: "The url the api backend can be accessed at from the frontend server",
format: "url",
default: "http://localhost:3001",
env: "API_URL",
},
},
nextAuth: {
secret: {
doc: "A random string used to hash tokens, sign cookies and generate crytographic keys. Shared with the api backend.",
format: String,
default: undefined,
env: "NEXTAUTH_SECRET",
sensitive: true,
},
audience: {
doc: "We will add this string as the `aud` claim to our JWT token, if empty or not present defaults to `frontend.url`",
format: String,
default: "",
env: "NEXTAUTH_AUDIENCE",
},
signingKeyB64: {
doc: "A base64 encoded JWK.Key used for JWT signing",
format: String,
default: undefined,
env: "NEXTAUTH_SIGNING_KEY_B64",
sensitive: true,
},
encryptionKeyB64: {
doc: "A base64 encoded JWK.Key used for JWT encryption",
format: String,
default: undefined,
env: "NEXTAUTH_ENCRYPTION_KEY_B64",
sensitive: true,
},
signingKey: {
doc: "",
format: String,
default: undefined,
sensitive: true,
skipGenerate: true,
},
encryptionKey: {
doc: "",
format: String,
default: undefined,
sensitive: true,
skipGenerate: true,
},
google: {
id: {
doc: "reference https://next-auth.js.org/providers/google",
format: String,
default: undefined,
env: "GOOGLE_ID",
sensitive: true,
},
secret: {
doc: "reference https://next-auth.js.org/providers/google",
format: String,
default: undefined,
env: "GOOGLE_SECRET",
sensitive: true,
},
},
github: {
id: {
doc: "reference https://next-auth.js.org/providers/github",
format: String,
default: undefined,
env: "GITHUB_ID",
sensitive: true,
},
secret: {
doc: "reference https://next-auth.js.org/providers/github",
format: String,
default: undefined,
env: "GITHUB_SECRET",
sensitive: true,
},
},
gitlab: {
id: {
doc: "reference https://next-auth.js.org/providers/gitlab",
format: String,
default: undefined,
env: "GITLAB_ID",
sensitive: true,
},
secret: {
doc: "reference https://next-auth.js.org/providers/gitlab",
format: String,
default: undefined,
env: "GITLAB_SECRET",
sensitive: true,
},
},
cognito: {
id: {
doc: "reference https://next-auth.js.org/providers/cognito",
format: String,
default: undefined,
env: "COGNITO_ID",
sensitive: true,
},
secret: {
doc: "reference https://next-auth.js.org/providers/cognito",
format: String,
default: undefined,
env: "COGNITO_SECRET",
sensitive: true,
},
domain: {
doc: "reference https://next-auth.js.org/providers/cognito",
format: String,
default: undefined,
env: "COGNITO_DOMAIN",
sensitive: true,
},
},
},
cfaccess: {
audience: {
doc: "the cloudflare access audience id",
format: String,
default: undefined,
env: "CFACCESS_AUDIENCE",
},
domain: {
doc: "the cloudflare access domain, something like `YOURAPP.cloudflareaccess.com`",
format: String,
default: undefined,
env: "CFACCESS_DOMAIN",
},
},
signald: {
enabled: {
doc: "Whether to enable the signald signal backend",
format: "Boolean",
default: false,
env: "SIGNALD_ENABLED",
},
socket: {
doc: "the unix domain socket signald is listening on",
format: String,
default: `${process.cwd()}/signald/signald.sock`,
env: "SIGNALD_SOCKET",
},
},
};
// define the interfaces for the concrete config objects
export interface IDBConfig {
connection: string;
name: string;
owner: string;
}
export interface IWorkerConfig {
connection: string;
concurrency: number;
pollInterval: number;
}
export interface IPostgraphileConfig {
auth: string;
visitor: string;
appRootConnection: string;
authConnection: string;
schema: string;
enableGraphiql: boolean;
}
export interface IDevConfig {
shadowConnection: string;
rootConnection: string;
}
export interface IFrontendConfig {
url: string;
apiUrl: string;
}
export interface INextAuthConfig {
secret: string;
audience: string;
signingKey: string;
encryptionKey: string;
signingKeyB64: string;
encryptionKeyB64: string;
google?: { id: string; secret: string };
github?: { id: string; secret: string };
gitlab?: { id: string; secret: string };
cognito?: { id: string; secret: string; domain: string };
}
export interface ICFAccessConfig {
audience: string;
domain: string;
}
export interface ISignaldConifg {
enabled: boolean;
socket: string;
}
// Extend the metamigo base type to add your app's custom config along side the out
// of the box Metamigo config
export interface IAppConfig extends Metamigo.IMetamigoConfig {
db: IDBConfig;
worker: IWorkerConfig;
postgraphile: IPostgraphileConfig;
dev: IDevConfig;
frontend: IFrontendConfig;
nextAuth: INextAuthConfig;
cfaccess: ICFAccessConfig;
signald: ISignaldConifg;
}
export type IAppConvict = Metamigo.ExtendedConvict<IAppConfig>;
// Merge the Metamigo base schema with your app's schmea
// @ts-ignore
export const schema: convict.Schema<IAppConfig> = {
...Metamigo.configBaseSchema,
...configSchema,
};
export const loadConfig = async (): Promise<IAppConfig> => {
const config = await Metamigo.loadConfiguration(schema);
if (!config.frontend.url || config.frontend.url === "")
throw new Error(
"configuration value frontend.url is missing. Add to config or set NEXTAUTH_URL env var"
);
// nextauth expects the url to be provided with this environment variable, so we will munge it in place here
process.env.NEXTAUTH_URL = config.frontend.url;
if (config.nextAuth.signingKeyB64)
config.nextAuth.signingKey = Buffer.from(
config.nextAuth.signingKeyB64,
"base64"
).toString("utf-8");
if (config.nextAuth.encryptionKeyB64)
config.nextAuth.encryptionKey = Buffer.from(
config.nextAuth.encryptionKeyB64,
"base64"
).toString("utf-8");
if (!config.nextAuth.audience || config.nextAuth.audience === "")
config.nextAuth.audience = config.frontend.url;
return config as any;
};
export const loadConfigRaw = async (): Promise<IAppConvict> => {
return Metamigo.loadConfigurationRaw(schema);
};
const config = defState("config", {
start: loadConfig,
});
export default config;