appstore-cli
Version:
A command-line interface (CLI) to interact with the Apple App Store Connect API.
74 lines (61 loc) • 2.08 kB
text/typescript
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
import { Config, getPrivateKey } from './config.js';
export async function generateJwt(config: Config): Promise<string> {
// Validate required configuration fields
if (!config.issuerId) {
throw new Error('Issuer ID is required but not provided');
}
if (!config.keyId) {
throw new Error('Key ID is required but not provided');
}
// Get private key from secure storage
const privateKey = await getPrivateKey(config);
if (!privateKey) {
throw new Error('Private key is required but not provided');
}
// Create a proper key object for the JWT library
let keyObject;
try {
keyObject = crypto.createPrivateKey(privateKey);
} catch (keyError) {
// Sanitize error message to avoid exposing key information
throw new Error('Invalid private key format');
}
const payload = {
iss: config.issuerId,
exp: Math.floor(Date.now() / 1000) + (20 * 60), // 20 minutes
aud: 'appstoreconnect-v1'
};
// Validate payload fields
if (!payload.iss) {
throw new Error('Issuer ID is required in JWT payload');
}
const currentTime = Math.floor(Date.now() / 1000);
if (!payload.exp || payload.exp <= currentTime) {
throw new Error('Invalid expiration time in JWT payload');
}
// Ensure expiration doesn't exceed 30 minutes (with 10-minute buffer)
if (payload.exp - currentTime > 30 * 60) {
throw new Error('Token expiration time exceeds maximum allowed duration');
}
const options: jwt.SignOptions = {
algorithm: 'ES256',
header: {
alg: 'ES256',
kid: config.keyId,
typ: 'JWT'
}
};
try {
const token = jwt.sign(payload, keyObject, options);
// Additional validation that token was created successfully
if (!token || typeof token !== 'string') {
throw new Error('Failed to generate JWT token');
}
return token;
} catch (signError) {
// Sanitize error message to avoid exposing key information
throw new Error('Failed to sign JWT token');
}
}