/**
 * HMAC-signed token bound to a pending pre-signed asset upload.
 *
 * Flow: presign mints a token + signed PUT URL; the agent uploads bytes to
 * R2 directly, then calls `finalize_asset_upload({ token })`. The token
 * carries enough state for finalize to find the pending blob, re-check the
 * caller's authz, and register the asset — without trusting any arg from the
 * agent. See ~/.claude/plans/mcp-presigned-asset-upload.md.
 *
 * Signed with `STUDIOGRAPH_JWT_SECRET` via `authService.getJwtSecret()`.
 * Reusing the workspace's JWT secret is deliberate: one rotation lever, one
 * audit boundary, and Phase 4 already env-requires it in production.
 *
 * The token is a bearer credential — anything that holds it can finalize as
 * the original presigner. R1 in the plan closes the leak-and-replay vector
 * by binding `payload.uploaderUserId` to the calling user at finalize.
 */
import jwt from 'jsonwebtoken';
import { TokenInvalidError } from './asset-upload-errors.js';
import { presignMimeAllowlist } from '../core/format-registry.js';
const TOKEN_TTL_SECONDS = 15 * 60;
export function signAssetUploadToken(payload, secret) {
    return jwt.sign(payload, secret, {
        algorithm: 'HS256',
        expiresIn: TOKEN_TTL_SECONDS,
    });
}
/**
 * Verify a token and return the payload. Throws `TokenInvalidError` on:
 *   - tamper (any field mutated, including assetId)
 *   - wrong secret (rotation mid-flight)
 *   - expired (>15min since presign)
 *   - malformed (not a JWT, wrong shape)
 *
 * Caller (finalize) is responsible for the post-verify checks:
 *   - R1 identity bind: assert `String(user.id) === payload.uploaderUserId`
 *   - re-check `canWriteRepo(user, payload.repo, ...)`
 *   - HEAD the pending blob and validate ct + size
 */
export function verifyAssetUploadToken(token, secret) {
    let decoded;
    try {
        decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
    }
    catch (err) {
        throw new TokenInvalidError(err instanceof Error ? err.message : 'token verification failed');
    }
    if (!isPayload(decoded)) {
        throw new TokenInvalidError('token payload has unexpected shape');
    }
    return {
        assetId: decoded.assetId,
        repo: decoded.repo,
        ownerType: decoded.ownerType,
        ownerId: decoded.ownerId,
        uploaderUserId: decoded.uploaderUserId,
        mime: decoded.mime,
        filename: decoded.filename,
    };
}
function isPayload(value) {
    if (!value || typeof value !== 'object')
        return false;
    const v = value;
    return (typeof v.assetId === 'string' &&
        typeof v.repo === 'string' &&
        typeof v.ownerType === 'string' &&
        typeof v.ownerId === 'string' &&
        typeof v.uploaderUserId === 'string' &&
        // Registry-derived presign-safe set (P4-V1). A tampered token carrying a
        // non-presign-safe mime (svg, pdf, …) still fails validation here.
        typeof v.mime === 'string' && presignMimeAllowlist().includes(v.mime) &&
        typeof v.filename === 'string');
}
//# sourceMappingURL=asset-upload-token.js.map