1- import { Args , Flags } from "@oclif/core" ;
2- import * as Ably from "ably" ;
1+ import { Flags } from "@oclif/core" ;
32import * as https from "node:https" ;
43
54import { AblyBaseCommand } from "../../base-command.js" ;
@@ -8,43 +7,62 @@ import { formatLabel, formatResource } from "../../utils/output.js";
87import { promptForConfirmation } from "../../utils/prompt-confirmation.js" ;
98
109export 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