diff --git a/cli/src/cli/ottofms.ts b/cli/src/cli/ottofms.ts index 90feda1..c8c64c3 100644 --- a/cli/src/cli/ottofms.ts +++ b/cli/src/cli/ottofms.ts @@ -1,9 +1,11 @@ import * as clack from "@clack/prompts"; -import axios from "axios"; +import axios, { AxiosError } from "axios"; import chalk from "chalk"; import open from "open"; import randomstring from "randomstring"; +import { abortIfCancel } from "./utils.js"; + interface WizardResponse { token: string; } @@ -144,23 +146,70 @@ export async function createDataAPIKey({ clack.log.info( `${chalk.cyan("Creating a Data API Key")}\nEnter FileMaker credentials for ${chalk.bold(filename)}.\n${chalk.dim(`The account must have the fmrest extended privilege enabled.`)}` ); - const username = await clack.text({ - message: `Enter the account name for ${chalk.bold(filename)}`, - }); - const password = await clack.password({ - message: `Enter the password for ${chalk.bold(filename)}`, - }); + while (true) { + const username = abortIfCancel( + await clack.text({ + message: `Enter the account name for ${chalk.bold(filename)}`, + }) + ); + + const password = abortIfCancel( + await clack.password({ + message: `Enter the password for ${chalk.bold(username)}`, + }) + ); + + try { + const response = await axios.post( + `${url.origin}/otto/api/api-key/create-only`, + { + database: filename, + label: "For FM Web App", + user: username, + pass: password, + } + ); - const response = await axios.post( - `${url.origin}/otto/api/api-key/create-only`, - { - database: filename, - label: "For FM Web App", - user: username, - pass: password, + return { apiKey: response.data.response.key }; + } catch (error) { + if (!(error instanceof AxiosError)) { + clack.log.error( + `${chalk.red("Error creating Data API key:")} Unknown error` + ); + } else { + const respMsg = + error.response?.data && "messages" in error.response.data + ? (error.response.data as { messages?: { text?: string }[] }) + .messages?.[0]?.text + : undefined; + + clack.log.error( + `${chalk.red("Error creating Data API key:")} ${ + respMsg ?? `Error code ${error.response?.status}` + } +${chalk.dim( + error.response?.status === 400 && + `Common reasons this might happen: +- The provided credentials are incorrect. +- The account does not have the fmrest extended privilege enabled. + +You may also want to try to create an API directly in the OttoFMS dashboard: +${url.origin}/otto/app/api-keys` +)} + ` + ); + } + const tryAgain = abortIfCancel( + await clack.confirm({ + message: "Do you want to try and enter credentials again?", + active: "Yes, try again", + inactive: "No, abort", + }) + ); + if (!tryAgain) { + throw new Error("User cancelled"); + } } - ); - - return { apiKey: response.data.response.key }; + } } diff --git a/cli/src/cli/utils.ts b/cli/src/cli/utils.ts index 4ef9339..434786f 100644 --- a/cli/src/cli/utils.ts +++ b/cli/src/cli/utils.ts @@ -45,8 +45,11 @@ Please run " ${npmName} init" first, or try this command again when inside a Pro }; export class UserAbortedError extends Error {} - -export function abortIfCancel(value: string | symbol): string { +export function abortIfCancel(value: symbol | string): string; +export function abortIfCancel(value: symbol | T): T; +export function abortIfCancel( + value: T | symbol +): T { if (isCancel(value)) { cancel(); throw new UserAbortedError(); diff --git a/cli/src/generators/auth.ts b/cli/src/generators/auth.ts index 894e1ea..3b94224 100644 --- a/cli/src/generators/auth.ts +++ b/cli/src/generators/auth.ts @@ -48,6 +48,6 @@ async function addProofkitAuth({ }: { emailProvider?: "plunk" | "resend"; }) { - await proofkitAuthInstaller({ emailProvider }); + await proofkitAuthInstaller(); mergeSettings({ auth: { type: "proofkit" } }); } diff --git a/cli/src/helpers/installDependencies.ts b/cli/src/helpers/installDependencies.ts index 09fe6e9..c4deaf3 100644 --- a/cli/src/helpers/installDependencies.ts +++ b/cli/src/helpers/installDependencies.ts @@ -2,6 +2,7 @@ import chalk from "chalk"; import { execa, type StdoutStderrOption } from "execa"; import ora, { type Ora } from "ora"; +import { state } from "~/state.js"; import { getUserPkgManager, type PackageManager, @@ -77,9 +78,9 @@ const runInstallCommand = async ( }; export const installDependencies = async ({ - projectDir, + projectDir = state.projectDir, }: { - projectDir: string; + projectDir?: string; }) => { logger.info("Installing dependencies..."); const pkgManager = getUserPkgManager(); diff --git a/cli/src/installers/dependencyVersionMap.ts b/cli/src/installers/dependencyVersionMap.ts index 3bcc0ac..56aa3d0 100644 --- a/cli/src/installers/dependencyVersionMap.ts +++ b/cli/src/installers/dependencyVersionMap.ts @@ -45,7 +45,7 @@ export const dependencyVersionMap = { "@clerk/themes": "^2.1.33", // FileMaker Data API - "@proofgeist/fmdapi": "^4.2.0", + "@proofgeist/fmdapi": "^4.2.1", // ProofKit "@proofgeist/kit": `^${getVersion()}`, @@ -60,6 +60,8 @@ export const dependencyVersionMap = { "@oslojs/binary": "^1.0.0", "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0", + "js-cookie": "^3.0.5", + "@types/js-cookie": "^3.0.6", // React Email "@react-email/components": "^0.0.28", diff --git a/cli/src/installers/proofkit-auth.ts b/cli/src/installers/proofkit-auth.ts index 68311ca..d99d4b1 100644 --- a/cli/src/installers/proofkit-auth.ts +++ b/cli/src/installers/proofkit-auth.ts @@ -3,9 +3,8 @@ import path from "path"; import { type OttoAPIKey } from "@proofgeist/fmdapi"; import chalk from "chalk"; import dotenv from "dotenv"; -import { execa } from "execa"; import fs from "fs-extra"; -import { type SourceFile } from "ts-morph"; +import { SyntaxKind, type SourceFile } from "ts-morph"; import { getLayouts } from "~/cli/fmdapi.js"; import { PKG_ROOT } from "~/consts.js"; @@ -20,11 +19,7 @@ import { formatAndSaveSourceFiles, getNewProject } from "~/utils/ts-morph.js"; import { addToHeaderSlot } from "./auth-shared.js"; import { installReactEmail } from "./react-email.js"; -export const proofkitAuthInstaller = async ({ - emailProvider, -}: { - emailProvider?: "plunk" | "resend"; -}) => { +export const proofkitAuthInstaller = async () => { const projectDir = state.projectDir; addPackageDependency({ projectDir, @@ -33,10 +28,17 @@ export const proofkitAuthInstaller = async ({ "@oslojs/binary", "@oslojs/crypto", "@oslojs/encoding", + "js-cookie", ], devMode: false, }); + addPackageDependency({ + projectDir, + dependencies: ["@types/js-cookie"], + devMode: true, + }); + // copy all files from template/extras/proofkit-auth to projectDir/src await fs.copy( path.join(PKG_ROOT, "template/extras/proofkit-auth"), @@ -106,6 +108,12 @@ export const proofkitAuthInstaller = async ({ }); await installReactEmail({ project }); + protectMainLayout( + project.addSourceFileAtPath( + path.join(projectDir, "src/app/(main)/layout.tsx") + ) + ); + await formatAndSaveSourceFiles(project); const hasProofKitLayouts = await checkForProofKitLayouts(projectDir); @@ -144,6 +152,27 @@ function addToSafeActionClient(sourceFile?: SourceFile) { ); } +function protectMainLayout(sourceFile: SourceFile) { + sourceFile.addImportDeclaration({ + defaultImport: "Protect", + moduleSpecifier: "@/components/auth/protect", + }); + + // inject query provider into the root layout + + const exportDefault = sourceFile.getFunction((dec) => dec.isDefaultExport()); + const bodyElement = exportDefault + ?.getBody() + ?.getFirstDescendantByKind(SyntaxKind.ReturnStatement) + ?.getFirstDescendantByKind(SyntaxKind.JsxElement); + + bodyElement?.replaceWithText( + ` + ${bodyElement?.getText()} + ` + ); +} + async function checkForProofKitLayouts(projectDir: string): Promise { const settings = getSettings(); diff --git a/cli/template/extras/proofkit-auth/app/(main)/auth/profile/reset-password-form.tsx b/cli/template/extras/proofkit-auth/app/(main)/auth/profile/reset-password-form.tsx index 7b38772..d9060d8 100644 --- a/cli/template/extras/proofkit-auth/app/(main)/auth/profile/reset-password-form.tsx +++ b/cli/template/extras/proofkit-auth/app/(main)/auth/profile/reset-password-form.tsx @@ -42,6 +42,7 @@ export default function UpdatePasswordForm() { label="Password" style={{ flexGrow: 1 }} value="••••••••" + readOnly />