Files
zeekay 2fc1aa3bbe Take an IAM token as the only bearer, and call out as eSign's own IAM identity
A call without a session carries a Hanzo IAM access token: signed by a key in
hanzo.id's JWKS, issued by hanzo.id, minted for this app (aud = IAM_CLIENT_ID)
and unexpired. One resolver, getBearer, sits behind /v1/rpc, /v1/rest and the
download routes, and acts in the team X-Team-Id names: a person as the account
their sign-in linked, an application of IAM_ORG as the team's organisation
owner. API tokens no longer get into the API; Zapier, which could carry nothing
else, is deleted.

Outbound, one Machine mints eSign's client_credentials token for the service it
is spent at. notify uses it as before, and every webhook now carries one minted
for its endpoint's origin, so a Hanzo receiver verifies the caller against
hanzo.id instead of holding a shared secret.
2026-10-20 18:04:07 -07:00

66 lines
2.0 KiB
TypeScript

import { SignJWT } from 'jose';
import { DateTime } from 'luxon';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { env } from '../../utils/env';
import { apiTokenByToken } from './api-token';
export type CreateEmbeddingPresignTokenOptions = {
apiToken: string;
/**
* Number of hours until the token expires
* In development mode, can be set to 0 to create a token that expires immediately (for testing)
*/
expiresIn?: number;
scope?: string;
};
export const createEmbeddingPresignToken = async ({
apiToken,
expiresIn,
scope,
}: CreateEmbeddingPresignTokenOptions) => {
try {
// Validate the API token
const validatedToken = await apiTokenByToken({ token: apiToken });
const now = DateTime.now();
// In development mode, allow setting expiresIn to 0 for testing
// In production, enforce a minimum expiration time
const isDevelopment = env('NODE_ENV') !== 'production';
const minExpirationMinutes = isDevelopment ? 0 : 5;
// Ensure expiresIn is at least the minimum allowed value
const effectiveExpiresIn =
expiresIn !== undefined && expiresIn >= minExpirationMinutes ? expiresIn : 60; // Default to 1 hour if not specified or below minimum
const expiresAt = now.plus({ minutes: effectiveExpiresIn });
const secret = new TextEncoder().encode(validatedToken.token);
const token = await new SignJWT({
aud: String(validatedToken.teamId ?? validatedToken.userId),
sub: String(validatedToken.id),
scope,
})
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt(now.toJSDate())
.setExpirationTime(expiresAt.toJSDate())
.sign(secret);
return {
token,
expiresAt: expiresAt.toJSDate(),
expiresIn: Math.floor(expiresAt.diff(now).toMillis() / 1000),
};
} catch (error) {
if (error instanceof AppError) {
throw error;
}
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
message: 'Failed to create presign token',
});
}
};