← 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;
}