← back to Filemaker Mcp

src/auth.js

55 lines

// Claris ID (Amazon Cognito) authentication for FileMaker Cloud.
// FileMaker Cloud does NOT accept Basic auth on the Data API — it requires a
// FileMaker ID (FMID) token obtained from Claris's Cognito user pool via the
// USER_SRP_AUTH flow. amazon-cognito-identity-js implements SRP for us.
//
// Flow:  Claris email+password --SRP--> Cognito idToken (valid ~1h)
// The idToken is then exchanged for a per-file Data API session token in fm-client.js.

import pkg from 'amazon-cognito-identity-js';
const { CognitoUserPool, CognitoUser, AuthenticationDetails } = pkg;

const pool = () =>
  new CognitoUserPool({
    UserPoolId: process.env.FM_COGNITO_USERPOOL || 'us-west-2_NqkuZcXQY',
    ClientId: process.env.FM_COGNITO_CLIENTID || '4l9rvl4mv5es1eep1qe97cautn',
  });

// Cache the idToken in-process; Cognito idTokens last ~1h, refresh a minute early.
let cache = { idToken: null, exp: 0 };

export function clearTokenCache() {
  cache = { idToken: null, exp: 0 };
}

export async function getIdToken() {
  const now = Date.now();
  if (cache.idToken && now < cache.exp - 60_000) return cache.idToken;

  const Username = process.env.FM_CLARIS_EMAIL;
  const Password = process.env.FM_CLARIS_PASSWORD;
  if (!Username || !Password) {
    throw new Error('FM_CLARIS_EMAIL / FM_CLARIS_PASSWORD are not set (route them via the secrets skill).');
  }

  const user = new CognitoUser({ Username, Pool: pool() });
  const details = new AuthenticationDetails({ Username, Password });

  const idToken = await new Promise((resolve, reject) => {
    user.authenticateUser(details, {
      onSuccess: (session) => resolve(session.getIdToken().getJwtToken()),
      onFailure: (err) => reject(new Error(`Claris ID auth failed: ${err.message || err}`)),
      // A dedicated service account must have MFA disabled, or these fire:
      mfaRequired: () =>
        reject(new Error('Claris ID account has MFA enabled. Disable MFA on the dedicated API account.')),
      totpRequired: () =>
        reject(new Error('Claris ID account requires TOTP. Disable MFA on the dedicated API account.')),
      newPasswordRequired: () =>
        reject(new Error('Claris ID account requires a new password. Set a permanent password first.')),
    });
  });

  cache = { idToken, exp: now + 60 * 60 * 1000 };
  return idToken;
}