Skip to content

Commit 855a69a

Browse files
committed
fix(auth): redesign revoke-token to use --client-id and --revocation-key flags
1 parent 6a35954 commit 855a69a

2 files changed

Lines changed: 181 additions & 168 deletions

File tree

src/commands/auth/revoke-token.ts

Lines changed: 63 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { Args, Flags } from "@oclif/core";
2-
import * as Ably from "ably";
1+
import { Flags } from "@oclif/core";
32
import * as https from "node:https";
43

54
import { AblyBaseCommand } from "../../base-command.js";
@@ -8,43 +7,62 @@ import { formatLabel, formatResource } from "../../utils/output.js";
87
import { promptForConfirmation } from "../../utils/prompt-confirmation.js";
98

109
export default class RevokeTokenCommand extends AblyBaseCommand {
11-
static args = {
12-
token: Args.string({
13-
description: "Token to revoke",
14-
name: "token",
15-
required: true,
16-
}),
17-
};
18-
19-
static description = "Revoke a token";
10+
static description = "Revoke tokens by client ID or revocation key";
2011

2112
static examples = [
22-
"$ ably auth revoke-token TOKEN",
23-
"$ ably auth revoke-token TOKEN --force",
24-
"$ ably auth revoke-token TOKEN --client-id clientid",
25-
"$ ably auth revoke-token TOKEN --json --force",
26-
"$ ably auth revoke-token TOKEN --pretty-json --force",
13+
"$ ably auth revoke-token --client-id user@example.com",
14+
"$ ably auth revoke-token --client-id user@example.com --force",
15+
"$ ably auth revoke-token --revocation-key group1",
16+
"$ ably auth revoke-token --client-id user@example.com --allow-reauth-margin",
17+
"$ ably auth revoke-token --client-id user@example.com --json --force",
2718
];
2819

2920
static flags = {
3021
...productApiFlags,
22+
...forceFlag,
3123
app: Flags.string({
3224
description: "The app ID or name (defaults to current app)",
3325
env: "ABLY_APP_ID",
3426
}),
35-
3627
"client-id": Flags.string({
3728
char: "c",
38-
description: "Revoke all tokens for given Client ID",
29+
description: "Revoke all tokens issued to this client ID",
30+
exclusive: ["revocation-key"],
31+
}),
32+
"revocation-key": Flags.string({
33+
char: "r",
34+
description:
35+
"Revoke all tokens matching this revocation key (JWT tokens only)",
36+
exclusive: ["client-id"],
37+
}),
38+
"allow-reauth-margin": Flags.boolean({
39+
default: false,
40+
description:
41+
"Delay enforcement by 30s so connected clients can obtain a new token before disconnection",
3942
}),
40-
...forceFlag,
4143
};
4244

43-
// Property to store the Ably client
44-
private ablyClient?: Ably.Realtime;
45-
4645
async run(): Promise<void> {
47-
const { args, flags } = await this.parse(RevokeTokenCommand);
46+
const { flags } = await this.parse(RevokeTokenCommand);
47+
48+
const clientId = flags["client-id"];
49+
const revocationKey = flags["revocation-key"];
50+
51+
// Require at least one target specifier
52+
if (!clientId && !revocationKey) {
53+
this.fail(
54+
"Either --client-id or --revocation-key is required. See https://ably.com/docs/auth/revocation for details.",
55+
flags,
56+
"revokeToken",
57+
);
58+
}
59+
60+
// Build target specifier
61+
const targetSpecifier = clientId
62+
? `clientId:${clientId}`
63+
: `revocationKey:${revocationKey}`;
64+
const targetLabel = clientId ? "Client ID" : "Revocation Key";
65+
const targetValue = (clientId ?? revocationKey)!;
4866

4967
// Get app and key
5068
const appAndKey = await this.ensureAppAndKey(flags);
@@ -53,7 +71,6 @@ export default class RevokeTokenCommand extends AblyBaseCommand {
5371
}
5472

5573
const { apiKey } = appAndKey;
56-
const { token } = args;
5774

5875
// JSON mode guard
5976
if (!flags.force && this.shouldOutputJson(flags)) {
@@ -66,19 +83,11 @@ export default class RevokeTokenCommand extends AblyBaseCommand {
6683

6784
// Interactive confirmation
6885
if (!flags.force && !this.shouldOutputJson(flags)) {
69-
this.log(`\nYou are about to revoke tokens matching:`);
70-
if (flags["client-id"]) {
71-
this.log(
72-
`${formatLabel("Client ID")} ${formatResource(flags["client-id"])}`,
73-
);
74-
} else {
75-
const truncatedToken =
76-
token.length > 15 ? token.slice(0, 15) + "..." : token;
77-
this.log(`${formatLabel("Token")} ${formatResource(truncatedToken)}`);
78-
}
86+
this.log(`\nYou are about to revoke all tokens matching:`);
87+
this.log(`${formatLabel(targetLabel)} ${formatResource(targetValue)}`);
7988

8089
const confirmed = await promptForConfirmation(
81-
"\nThis will permanently revoke this token and any applications using it need to be re-issued a new token. Are you sure?",
90+
"\nThis will permanently revoke all matching tokens, and any applications using those tokens will need to be issued new tokens. Are you sure?",
8291
);
8392

8493
if (!confirmed) {
@@ -88,48 +97,27 @@ export default class RevokeTokenCommand extends AblyBaseCommand {
8897
}
8998

9099
try {
91-
// Create Ably Realtime client
92-
const client = await this.createAblyRealtimeClient(flags);
93-
if (!client) return;
94-
95-
this.ablyClient = client;
96-
97-
const clientId = flags["client-id"] || token;
98-
99-
if (!flags["client-id"]) {
100-
// We need to warn the user that we're using the token as a client ID
101-
this.logWarning(
102-
"Revoking a specific token is only possible if it has a client ID or revocation key.",
103-
flags,
104-
);
105-
this.logWarning(
106-
"For advanced token revocation options, see: https://ably.com/docs/auth/revocation.",
107-
flags,
108-
);
109-
this.logWarning(
110-
"Using the token argument as a client ID for this operation.",
111-
flags,
112-
);
113-
}
114-
115100
// Extract the keyName (appId.keyId) from the API key
116101
const keyParts = apiKey.split(":");
117102
if (keyParts.length !== 2) {
118103
this.fail(
119104
"Invalid API key format. Expected format: appId.keyId:secret",
120105
flags,
121-
"tokenRevoke",
106+
"revokeToken",
122107
);
123108
}
124109

125-
const keyName = keyParts[0]!; // This gets the appId.keyId portion
110+
const keyName = keyParts[0]!;
126111
const secret = keyParts[1]!;
127112

128-
// Create the properly formatted body for token revocation
129-
const requestBody = {
130-
targets: [`clientId:${clientId}`],
113+
const requestBody: Record<string, unknown> = {
114+
targets: [targetSpecifier],
131115
};
132116

117+
if (flags["allow-reauth-margin"]) {
118+
requestBody.allowReauthMargin = true;
119+
}
120+
133121
try {
134122
// Make direct HTTPS request to Ably REST API
135123
const response = await this.makeHttpRequest(
@@ -143,27 +131,33 @@ export default class RevokeTokenCommand extends AblyBaseCommand {
143131
{
144132
revocation: {
145133
message: "Token revocation processed successfully",
134+
target: targetSpecifier,
146135
response,
147136
},
148137
},
149138
flags,
150139
);
151140
} else {
152-
this.logSuccessMessage("Token successfully revoked.", flags);
141+
this.logSuccessMessage(
142+
`Tokens matching ${targetLabel.toLowerCase()} ${formatResource(targetValue)} have been revoked.`,
143+
flags,
144+
);
153145
}
154146
} catch (requestError: unknown) {
155-
// Handle specific API errors
156147
const error = requestError as Error;
157148
if (error.message && error.message.includes("token_not_found")) {
158-
this.fail("Token not found or already revoked", flags, "tokenRevoke");
149+
this.fail(
150+
"No matching tokens found or already revoked",
151+
flags,
152+
"revokeToken",
153+
);
159154
} else {
160155
throw requestError;
161156
}
162157
}
163158
} catch (error) {
164-
this.fail(error, flags, "tokenRevoke");
159+
this.fail(error, flags, "revokeToken");
165160
}
166-
// Client cleanup is handled by base class finally() method
167161
}
168162

169163
// Helper method to make a direct HTTP request to the Ably REST API

0 commit comments

Comments
 (0)