@ably/cli
Version:
Ably CLI for Pub/Sub, Chat and Spaces
109 lines (108 loc) • 4.91 kB
JavaScript
import { Flags } from "@oclif/core";
import { randomUUID } from "node:crypto";
import { AblyBaseCommand } from "../../base-command.js";
export default class IssueAblyTokenCommand extends AblyBaseCommand {
static description = "Creates an Ably Token with capabilities";
static examples = [
"$ ably auth issue-ably-token",
'$ ably auth issue-ably-token --capability \'{"*":["*"]}\'',
'$ ably auth issue-ably-token --capability \'{"chat:*":["publish","subscribe"], "status:*":["subscribe"]}\' --ttl 3600',
"$ ably auth issue-ably-token --client-id client123 --ttl 86400",
'$ ably auth issue-ably-token --client-id "none" --ttl 3600',
"$ ably auth issue-ably-token --json",
"$ ably auth issue-ably-token --pretty-json",
"$ ably auth issue-ably-token --token-only",
'$ ably channels publish --token "$(ably auth issue-ably-token --token-only)" my-channel "Hello"',
];
static flags = {
...AblyBaseCommand.globalFlags,
app: Flags.string({
description: "App ID to use (uses current app if not specified)",
env: "ABLY_APP_ID",
}),
capability: Flags.string({
default: '{"*":["*"]}',
description: 'Capabilities JSON string (e.g. {"channel":["publish","subscribe"]})',
}),
"client-id": Flags.string({
description: 'Client ID to associate with the token. Use "none" to explicitly issue a token with no client ID, otherwise a default will be generated.',
}),
"token-only": Flags.boolean({
default: false,
description: "Output only the token string without any formatting or additional information",
}),
ttl: Flags.integer({
default: 3600, // 1 hour
description: "Time to live in seconds",
}),
};
async run() {
const { flags } = await this.parse(IssueAblyTokenCommand);
// Get app and key
const appAndKey = await this.ensureAppAndKey(flags);
if (!appAndKey) {
return;
}
const { apiKey } = appAndKey;
try {
// Parse capabilities
let capabilities;
try {
capabilities = JSON.parse(flags.capability);
}
catch (error) {
this.error(`Invalid capability JSON: ${error instanceof Error ? error.message : String(error)}`);
}
// Create token params
const tokenParams = {
capability: capabilities,
ttl: flags.ttl * 1000, // Convert to milliseconds for Ably SDK
};
// Handle client ID - use special "none" value to explicitly indicate no clientId
if (flags["client-id"]) {
if (flags["client-id"].toLowerCase() === "none") {
// No client ID - leave clientId undefined in the token params
}
else {
// Use the provided client ID
tokenParams.clientId = flags["client-id"];
}
}
else {
// Generate a default client ID
tokenParams.clientId = `ably-cli-${randomUUID().slice(0, 8)}`;
}
// Create Ably REST client and request token
const rest = await this.createAblyRestClient({ ...flags, "api-key": apiKey }, {
skipAuthInfo: flags["token-only"],
});
if (!rest) {
return;
}
const tokenRequest = await rest.auth.createTokenRequest(tokenParams);
// Use the token request to get an actual token
const tokenDetails = await rest.auth.requestToken(tokenRequest);
// If token-only flag is set, output just the token string
if (flags["token-only"]) {
this.log(tokenDetails.token);
return;
}
if (this.shouldOutputJson(flags)) {
this.log(this.formatJsonOutput({ capability: tokenDetails.capability }, flags));
}
else {
this.log("Generated Ably Token:");
this.log(`Token: ${tokenDetails.token}`);
this.log(`Type: Ably`);
this.log(`Issued: ${new Date(tokenDetails.issued).toISOString()}`);
this.log(`Expires: ${new Date(tokenDetails.expires).toISOString()}`);
this.log(`TTL: ${flags.ttl} seconds`);
this.log(`Client ID: ${tokenDetails.clientId || "None"}`);
this.log(`Capability: ${this.formatJsonOutput({ capability: tokenDetails.capability }, flags)}`);
}
}
catch (error) {
this.error(`Error issuing Ably token: ${error instanceof Error ? error.message : String(error)}`);
}
}
}