metamigo: do nextauth v3 -> v4 upgrades

This commit is contained in:
Abel Luck 2023-06-06 10:28:29 +00:00
parent a33f80c497
commit 45f8cb1234
13 changed files with 158 additions and 123 deletions

View file

@ -40,4 +40,9 @@ npm install --workspace=metamigo-common
```
turbo run build --filter metamigo-cli
```
npm run dev -- --filter @digiresilience/metamigo-cli
npm run migrate
```

View file

@ -11,6 +11,7 @@
"dependencies": {
"@digiresilience/montar": "*",
"@digiresilience/metamigo-config": "*",
"@digiresilience/metamigo-common": "*",
"@digiresilience/metamigo-db": "*",
"@digiresilience/metamigo-api": "*",
"@digiresilience/metamigo-worker": "*",
@ -32,7 +33,9 @@
"typescript": "^5.0.4"
},
"scripts": {
"migrate": "NODE_ENV=development node --unhandled-rejections=strict build/main/index.js db -- migrate",
"build": "tsc -p tsconfig.json",
"dev": "NODE_ENV=development node --unhandled-rejections=strict build/main/index.js api",
"cli": "NODE_ENV=development node --unhandled-rejections=strict build/main/index.js",
"fix:lint": "eslint src --ext .ts --fix",
"fmt": "prettier \"src/**/*.ts\" --write",

View file

@ -6,7 +6,7 @@ import { IAppConfig, IAppConvict } from "@digiresilience/metamigo-config";
import { loadConfigRaw } from "@digiresilience/metamigo-config";
export const genConf = async (): Promise<void> => {
const c = await loadConfigRaw() as any;
const c = (await loadConfigRaw()) as any;
const generated = generateConfig(c) as any;
console.log(generated);
};
@ -17,6 +17,6 @@ export const genSchema = async (): Promise<void> => {
};
export const listConfig = async (): Promise<void> => {
const c = await loadConfigRaw() as any;
const c = (await loadConfigRaw()) as any;
printConfigOptions(c);
};

View file

@ -11,7 +11,7 @@ import pg from "pg";
import { loadConfig } from "@digiresilience/metamigo-config";
import { getPostGraphileOptions } from "@digiresilience/metamigo-db";
const {Pool} = pg;
const { Pool } = pg;
export const exportGraphqlSchema = async (): Promise<void> => {
const config = await loadConfig();

View file

@ -1 +0,0 @@
METAMIGO_CONFIG=.metamigo.local.json

View file

@ -1,8 +1,5 @@
/* eslint-disable unicorn/no-null */
/* eslint-disable max-params */
import type { Adapter } from "next-auth/adapters";
// @ts-expect-error: Missing export
import type { AppOptions } from "next-auth";
import type { Adapter, AdapterAccount, AdapterSession, AdapterUser } from "next-auth/adapters";
import * as Wreck from "@hapi/wreck";
import * as Boom from "@hapi/boom";
@ -70,7 +67,7 @@ export const MetamigoAdapter = (config: IAppConfig): Adapter => {
json: "force",
});
async function getAdapter(_appOptions: AppOptions) {
function getAdapter(): Adapter {
async function createUser(profile: Profile) {
try {
if (!profile.createdBy) profile = { ...profile, createdBy: "nextauth" };
@ -106,19 +103,17 @@ export const MetamigoAdapter = (config: IAppConfig): Adapter => {
}
}
async function getUserByProviderAccountId(
providerId: string,
providerAccountId: string
) {
async function getUserByAccount({ providerAccountId, provider }: { providerAccountId: string, provider: string }) {
try {
const { payload } = await wreck.get(
`getUserByProviderAccountId/${providerId}/${providerAccountId}`
`getUserByAccount/${provider}/${providerAccountId}`
);
return payload;
} catch (error) {
if (Boom.isBoom(error, 404)) return null;
throw new Error("GET_USER_BY_PROVIDER_ACCOUNT_ID");
console.log(error)
throw new Error("GET_USER_BY_ACCOUNT");
}
}
@ -135,51 +130,39 @@ export const MetamigoAdapter = (config: IAppConfig): Adapter => {
}
async function linkAccount(
userId: string,
providerId: string,
providerType: string,
providerAccountId: string,
refreshToken: string,
accessToken: string,
accessTokenExpires: number
account: AdapterAccount
) {
try {
const payload = {
userId,
providerId,
providerType,
providerAccountId: `${providerAccountId}`, // must be a string
refreshToken,
accessToken,
accessTokenExpires,
};
await wreck.put("linkAccount", {
payload,
});
} catch {
await wreck.put("linkAccount", {payload: account} as any );
} catch(error) {
console.log(error);
throw new Error("LINK_ACCOUNT_ERROR");
}
}
async function createSession(user: User) {
try {
const { payload } = await wreck.post("createSession", {
const { payload }: {payload: AdapterSession} = await wreck.post("createSession", {
payload: user,
});
payload.expires = new Date(payload.expires)
return payload;
} catch {
} catch(error) {
console.log(error)
throw new Error("CREATE_SESSION_ERROR");
}
}
async function getSession(sessionToken: string) {
async function getSessionAndUser(sessionToken: string) {
try {
const { payload } = await wreck.get(`getSession/${sessionToken}`);
return payload;
const {payload}: {payload: any} = await wreck.get(`getSessionAndUser/${sessionToken}`);
const { session, user }: {session: AdapterSession, user: AdapterUser} = payload;
session.expires = new Date(session.expires)
return {session, user}
} catch (error) {
console.log(error)
if (Boom.isBoom(error, 404)) return null;
throw new Error("GET_SESSION_ERROR");
throw new Error("GET_SESSION_AND_USER_ERROR");
}
}
@ -213,21 +196,18 @@ export const MetamigoAdapter = (config: IAppConfig): Adapter => {
createUser,
getUser,
getUserByEmail,
getUserByProviderAccountId,
getUserByAccount,
updateUser,
// deleteUser,
linkAccount,
// unlinkAccount,
createSession,
getSession,
getSessionAndUser,
updateSession,
deleteSession,
// @ts-expect-error: Type error
} as AdapterInstance<Profile, User, Session, unknown>;
}
return {
// @ts-expect-error: non-existent property
getAdapter,
};
return getAdapter();
};

View file

@ -7,6 +7,7 @@ import Cognito from "next-auth/providers/cognito";
import { loadConfig, IAppConfig } from "@digiresilience/metamigo-config";
import { MetamigoAdapter } from "../../../lib/nextauth-adapter";
import { CloudflareAccessProvider } from "../../../lib/cloudflare";
import { AdapterSession, AdapterUser } from "next-auth/adapters";
const nextAuthOptions = (config: IAppConfig, req: NextApiRequest) => {
const { nextAuth, cfaccess } = config;
@ -72,11 +73,6 @@ const nextAuthOptions = (config: IAppConfig, req: NextApiRequest) => {
providers,
adapter,
callbacks: {
async session(session: any, token: any) {
// make the user id available in the react client
session.user.id = token.userId;
return session;
},
async jwt(token: any, user: any) {
const isSignIn = Boolean(user);
// Add auth_time to token on signin in

45
package-lock.json generated
View file

@ -13,6 +13,7 @@
"packages/*"
],
"devDependencies": {
"dotenv-cli": "latest",
"prettier": "^2.8.8"
}
},
@ -8189,8 +8190,7 @@
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"dev": true
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="
},
"node_modules/comma-separated-tokens": {
"version": "2.0.3",
@ -8433,7 +8433,6 @@
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
"integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
"dev": true,
"engines": {
"node": "*"
}
@ -8964,6 +8963,30 @@
"node": ">=12"
}
},
"node_modules/dotenv-cli": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.2.1.tgz",
"integrity": "sha512-ODHbGTskqRtXAzZapDPvgNuDVQApu4oKX8lZW7Y0+9hKA6le1ZJlyRS687oU9FXjOVEDU/VFV6zI125HzhM1UQ==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.3",
"dotenv": "^16.0.0",
"dotenv-expand": "^10.0.0",
"minimist": "^1.2.6"
},
"bin": {
"dotenv": "cli.js"
}
},
"node_modules/dotenv-expand": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
"integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"node_modules/duplexer2": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
@ -10233,8 +10256,7 @@
"node_modules/fast-copy": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz",
"integrity": "sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA==",
"dev": true
"integrity": "sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA=="
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
@ -10319,8 +10341,7 @@
"node_modules/fast-safe-stringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
"dev": true
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
},
"node_modules/fastq": {
"version": "1.15.0",
@ -11635,7 +11656,6 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/help-me/-/help-me-4.2.0.tgz",
"integrity": "sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==",
"dev": true,
"dependencies": {
"glob": "^8.0.0",
"readable-stream": "^3.6.0"
@ -11645,7 +11665,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
@ -11654,7 +11673,6 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@ -11673,7 +11691,6 @@
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
@ -11685,7 +11702,6 @@
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dev": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@ -11699,7 +11715,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dev": true,
"dependencies": {
"safe-buffer": "~5.2.0"
}
@ -14216,7 +14231,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
"integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
"dev": true,
"engines": {
"node": ">=10"
}
@ -16980,7 +16994,6 @@
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.0.0.tgz",
"integrity": "sha512-zKFjYXBzLaLTEAN1ayKpHXtL5UeRQC7R3lvhKe7fWs7hIVEjKGG/qIXwQt9HmeUp71ogUd/YcW+LmMwRp4KT6Q==",
"dev": true,
"dependencies": {
"colorette": "^2.0.7",
"dateformat": "^4.6.3",
@ -17005,7 +17018,6 @@
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.0.tgz",
"integrity": "sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg==",
"dev": true,
"dependencies": {
"abort-controller": "^3.0.0",
"buffer": "^6.0.3",
@ -21976,6 +21988,7 @@
"next-auth": "^4.22.1",
"pg-promise": "^11.4.3",
"pino": "^8.14.1",
"pino-pretty": "^10.0.0",
"prom-client": "^14.x.x",
"uuid": "^9.0.0"
},

View file

@ -4,7 +4,8 @@
"description": "",
"main": "index.js",
"scripts": {
"dev": "dotenv -- turbo run dev"
"dev": "dotenv -- turbo run dev",
"migrate": "dotenv -- npm run migrate --workspace=@digiresilience/metamigo-cli"
},
"packageManager": "npm@9.3.1",
"workspaces": [
@ -16,7 +17,7 @@
"url": "git+https://gitlab.com/digiresilience/link/link-stack.git"
},
"author": "Darren Clarke",
"license": "ISC",
"dlicense": "ISC",
"overrides": {
"@mui/styles": {
"react": "18.2.0"
@ -29,6 +30,7 @@
}
},
"devDependencies": {
"prettier": "^2.8.8"
"prettier": "^2.8.8",
"dotenv-cli": "latest"
}
}

View file

@ -13,7 +13,7 @@ const minimumProfileSchema = Joi.object()
const minimumUserSchema = Joi.object()
.keys({
id: Joi.string().required(),
userId: Joi.string().required(),
email: Joi.string().email().required(),
})
.unknown(true);

View file

@ -93,13 +93,13 @@ export const register = async <TUser, TProfile>(
},
{
method: "GET",
path: `${basePath}/getUserByProviderAccountId/{providerId}/{providerAccountId}`,
path: `${basePath}/getUserByAccount/{provider}/{providerAccountId}`,
options: {
auth,
tags,
validate: {
params: {
providerId: Joi.string(),
provider: Joi.string(),
providerAccountId: Joi.string(),
},
},
@ -107,10 +107,10 @@ export const register = async <TUser, TProfile>(
request: Hapi.Request,
h: ResponseToolkit
): Promise<ResponseObject> {
const { providerId, providerAccountId } = request.params;
const { provider, providerAccountId } = request.params;
const r = await opts
.nextAuthAdapterFactory(request)
.getUserByProviderAccountId(providerId, providerAccountId);
.getUserByAccount(provider, providerAccountId);
if (!r) return h.response().code(404);
return h.response(r as object);
},
@ -148,14 +148,15 @@ export const register = async <TUser, TProfile>(
tags,
validate: {
payload: Joi.object({
userId,
providerId: Joi.string(),
providerType: Joi.string(),
providerAccountId: Joi.string(),
refreshToken: Joi.string().optional().allow(null),
accessToken: Joi.string().optional().allow(null),
accessTokenExpires: Joi.number().optional().allow(null),
}).options({ presence: "required" }),
// https://next-auth.js.org/getting-started/upgrade-v4#schema-changes
userId: Joi.string().required(),
provider: Joi.string().required(),
type: Joi.string().required(),
providerAccountId: Joi.string().required(),
refresh_token: Joi.string().optional().allow(null),
access_token: Joi.string().optional().allow(null),
expires_at: Joi.number().optional().allow(null),
}).unknown(true),
},
async handler(
request: Hapi.Request,
@ -193,7 +194,11 @@ export const register = async <TUser, TProfile>(
auth,
tags,
validate: {
payload: user,
payload: Joi.object({
userId: Joi.string().required(),
sessionToken: Joi.string().required(),
expires: Joi.string().isoDate().required(),
}),
},
async handler(
request: Hapi.Request,
@ -210,7 +215,7 @@ export const register = async <TUser, TProfile>(
},
{
method: "GET",
path: `${basePath}/getSession/{sessionToken}`,
path: `${basePath}/getSessionAndUser/{sessionToken}`,
options: {
auth,
tags,
@ -226,7 +231,7 @@ export const register = async <TUser, TProfile>(
const token = request.params.sessionToken;
const r = await opts
.nextAuthAdapterFactory(request)
.getSession(token);
.getSessionAndUser(token);
if (!r) return h.response().code(404);
return h.response(r as object);
},

View file

@ -1,10 +1,16 @@
/* eslint-disable unicorn/no-null,max-params */
import { createHash, randomBytes } from "node:crypto";
import omit from "lodash/omit.js";
import type { IMetamigoRepositories } from "../records/index.js";
import { IMetamigoRepositories, idKeysOf } from "../records/index.js";
import type { UnsavedAccount } from "../records/account.js";
import type { UserId, UnsavedUser, SavedUser } from "../records/user.js";
import type { UnsavedSession, SavedSession } from "../records/session.js";
import {
AdapterAccount,
AdapterSession,
AdapterUser,
} from "next-auth/adapters.js";
import { ReadableStreamDefaultController } from "stream/web";
// Sessions expire after 30 days of being idle
export const defaultSessionMaxAge = 30 * 24 * 60 * 60 * 1000;
@ -23,7 +29,7 @@ export class NextAuthAdapter<TRepositories extends IMetamigoRepositories> {
private repos: TRepositories,
private readonly sessionMaxAge = defaultSessionMaxAge,
private readonly sessionUpdateAge = defaulteSessionUpdateAge
) { }
) {}
async createUser(profile: UnsavedUser): Promise<SavedUser> {
// @ts-expect-error Typescript doesn't like lodash's omit()
@ -56,12 +62,12 @@ export class NextAuthAdapter<TRepositories extends IMetamigoRepositories> {
return user;
}
async getUserByProviderAccountId(
providerId: string,
async getUserByAccount(
provider: string,
providerAccountId: string
): Promise<SavedUser | null> {
const account = await this.repos.accounts.findBy({
compoundId: getCompoundId(providerId, providerAccountId),
compoundId: getCompoundId(provider, providerAccountId),
});
if (!account) return null;
@ -72,15 +78,16 @@ export class NextAuthAdapter<TRepositories extends IMetamigoRepositories> {
return this.repos.users.update(user);
}
async linkAccount(
userId: string,
providerId: string,
providerType: string,
providerAccountId: string,
refreshToken: string,
accessToken: string,
accessTokenExpires: number
): Promise<void> {
async linkAccount(adapterAccount: AdapterAccount): Promise<void> {
const {
userId,
access_token: accessToken,
refresh_token: refreshToken,
provider: providerId,
providerAccountId,
expires_at: accessTokenExpires,
type: providerType,
} = adapterAccount;
const exists = await this.repos.users.existsById({ id: userId });
if (!exists) return;
const account: UnsavedAccount = {
@ -109,7 +116,13 @@ export class NextAuthAdapter<TRepositories extends IMetamigoRepositories> {
});
}
createSession(user: SavedUser): Promise<SavedSession> {
createSession({
sessionToken,
userId,
}: {
sessionToken: string;
userId: string;
}): Promise<SavedSession> {
let expires;
if (this.sessionMaxAge) {
const dateExpires = new Date(Date.now() + this.sessionMaxAge);
@ -118,22 +131,41 @@ export class NextAuthAdapter<TRepositories extends IMetamigoRepositories> {
const session: UnsavedSession = {
expires,
userId: user.id,
sessionToken: randomToken(),
userId,
sessionToken,
//sessionToken: randomToken(),
accessToken: randomToken(),
};
return this.repos.sessions.insert(session);
}
async getSession(sessionToken: string): Promise<SavedSession | null> {
async getSessionAndUser(
sessionToken: string
): Promise<{ session: AdapterSession; user: AdapterUser } | null> {
const session = await this.repos.sessions.findBy({ sessionToken });
if (!session) return null;
if (session && session.expires && new Date() > session.expires) {
this.repos.sessions.remove(session);
return null;
}
return session;
const user = await this.repos.users.findById({ id: session.userId });
if (!user) return null;
const adapterSession: AdapterSession = {
userId: session.userId,
expires: session.expires,
sessionToken: sessionToken,
};
const adapterUser: AdapterUser = {
id: user.id,
email: user.email,
emailVerified: user.emailVerified,
};
return { session: adapterSession, user: adapterUser };
}
async updateSession(

View file

@ -9,20 +9,20 @@ export const configSchema = {
doc: "The postgres connection url.",
format: "uri",
default: "postgresql://metamigo:metamigo@127.0.0.1:5435/metamigo_dev",
env: "DATABASE_URL",
env: "METAMIGO_DATABASE_URL",
sensitive: true,
},
name: {
doc: "The name of the postgres database",
format: String,
default: "metamigo_dev",
env: "DATABASE_NAME",
env: "METAMIGO_DATABASE_NAME",
},
owner: {
doc: "The username of the postgres database owner",
format: String,
default: "metamigo",
env: "DATABASE_OWNER",
env: "METAMIGO_DATABASE_OWNER",
},
},
worker: {
@ -30,19 +30,19 @@ export const configSchema = {
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",
env: "METAMIGO_WORKER_DATABASE_URL",
},
concurrency: {
doc: "The number of jobs to run concurrently",
default: 1,
format: "positiveInt",
env: "WORKER_CONCURRENT_JOBS",
env: "METAMIGO_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",
env: "METAMIGO_WORKER_POLL_INTERVAL_MS",
},
},
postgraphile: {
@ -50,26 +50,26 @@ export const configSchema = {
doc: "The postgres role that postgraphile logs in with",
format: String,
default: "metamigo_graphile_auth",
env: "DATABASE_AUTHENTICATOR",
env: "METAMIGO_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",
env: "METAMIGO_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",
env: "METAMIGO_DATABASE_AUTH_URL",
},
visitor: {
doc: "The postgres role that postgraphile switches to",
format: String,
default: "app_postgraphile",
env: "DATABASE_VISITOR",
env: "METAMIGO_DATABASE_VISITOR",
},
schema: {
doc: "The schema postgraphile should expose with graphql",
@ -80,7 +80,7 @@ export const configSchema = {
doc: "Whether to enable the graphiql web interface or not",
format: "Boolean",
default: false,
env: "ENABLE_GRAPHIQL",
env: "METAMIGO_ENABLE_GRAPHIQL",
},
},
@ -89,14 +89,14 @@ export const configSchema = {
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",
env: "METAMIGO_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",
env: "METAMIGO_ROOT_DATABASE_URL",
sensitive: true,
},
},
@ -105,13 +105,13 @@ export const configSchema = {
doc: "The url the frontend can be accessed at",
format: "url",
default: "http://localhost:3000",
env: "FRONTEND_URL",
env: "METAMIGO_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",
env: "METAMIGO_API_URL",
},
},
nextAuth: {