7
7
from safety .console import main_console as console
8
8
from safety .constants import MSG_FINISH_REGISTRATION_TPL , MSG_VERIFICATION_HINT
9
9
from safety .meta import get_version
10
+ from safety .decorators import notify
10
11
11
12
try :
12
13
from typing import Annotated
27
28
get_token ,
28
29
)
29
30
from safety .auth .server import process_browser_callback
31
+ from safety .util import initialize_event_bus
30
32
from safety .scan .constants import (
31
33
CLI_AUTH_COMMAND_HELP ,
32
34
CLI_AUTH_HEADLESS_HELP ,
37
39
)
38
40
39
41
from ..cli_util import SafetyCLISubGroup , get_command_for , pass_safety_cli_obj
42
+ from safety .error_handlers import handle_cmd_exception
40
43
from .constants import (
41
44
MSG_FAIL_LOGIN_AUTHED ,
42
45
MSG_FAIL_REGISTER_AUTHED ,
50
53
auth_app = Typer (rich_markup_mode = "rich" , name = "auth" )
51
54
52
55
53
-
54
56
CMD_LOGIN_NAME = "login"
55
57
CMD_REGISTER_NAME = "register"
56
58
CMD_STATUS_NAME = "status"
57
59
CMD_LOGOUT_NAME = "logout"
58
60
DEFAULT_CMD = CMD_LOGIN_NAME
59
61
60
- @auth_app .callback (invoke_without_command = True ,
61
- cls = SafetyCLISubGroup ,
62
- help = CLI_AUTH_COMMAND_HELP ,
63
- epilog = DEFAULT_EPILOG ,
64
- context_settings = {"allow_extra_args" : True ,
65
- "ignore_unknown_options" : True })
62
+
63
+ @auth_app .callback (
64
+ invoke_without_command = True ,
65
+ cls = SafetyCLISubGroup ,
66
+ help = CLI_AUTH_COMMAND_HELP ,
67
+ epilog = DEFAULT_EPILOG ,
68
+ context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True },
69
+ )
66
70
@pass_safety_cli_obj
67
71
def auth (ctx : typer .Context ) -> None :
68
72
"""
@@ -71,12 +75,11 @@ def auth(ctx: typer.Context) -> None:
71
75
Args:
72
76
ctx (typer.Context): The Typer context object.
73
77
"""
74
- LOG .info (' auth started' )
78
+ LOG .info (" auth started" )
75
79
76
80
# If no subcommand is invoked, forward to the default command
77
81
if not ctx .invoked_subcommand :
78
- default_command = get_command_for (name = DEFAULT_CMD ,
79
- typer_instance = auth_app )
82
+ default_command = get_command_for (name = DEFAULT_CMD , typer_instance = auth_app )
80
83
return ctx .forward (default_command )
81
84
82
85
@@ -99,6 +102,7 @@ def fail_if_authenticated(ctx: typer.Context, with_msg: str) -> None:
99
102
console .print (with_msg .format (email = email ))
100
103
sys .exit (0 )
101
104
105
+
102
106
def render_email_note (auth : Auth ) -> str :
103
107
"""
104
108
Renders a note indicating whether email verification is required.
@@ -111,8 +115,8 @@ def render_email_note(auth: Auth) -> str:
111
115
"""
112
116
return "" if auth .email_verified else "[red](email verification required)[/red]"
113
117
114
- def render_successful_login ( auth : Auth ,
115
- organization : Optional [str ] = None ) -> None :
118
+
119
+ def render_successful_login ( auth : Auth , organization : Optional [str ] = None ) -> None :
116
120
"""
117
121
Renders a message indicating a successful login.
118
122
@@ -132,34 +136,34 @@ def render_successful_login(auth: Auth,
132
136
details = [f"[green][bold]Account:[/bold] { email } [/green] { email_note } " ]
133
137
134
138
if organization :
135
- details .insert (0 ,
136
- "[green][bold]Organization:[/bold] " \
137
- f"{ organization } [green]" )
139
+ details .insert (0 , f"[green][bold]Organization:[/bold] { organization } [green]" )
138
140
139
141
for msg in details :
140
142
console .print (Padding (msg , (0 , 0 , 0 , 1 )), emoji = True )
141
143
142
144
143
145
@auth_app .command (name = CMD_LOGIN_NAME , help = CLI_AUTH_LOGIN_HELP )
146
+ @handle_cmd_exception
147
+ @notify
144
148
def login (
145
149
ctx : typer .Context ,
146
150
headless : Annotated [
147
151
Optional [bool ],
148
152
typer .Option (
149
153
"--headless" ,
150
154
help = CLI_AUTH_HEADLESS_HELP ,
151
- )
152
- ] = None
155
+ ),
156
+ ] = None ,
153
157
) -> None :
154
- headless = headless is True
158
+ headless = headless is True
155
159
"""
156
160
Authenticate Safety CLI with your safetycli.com account using your default browser.
157
161
158
162
Args:
159
163
ctx (typer.Context): The Typer context object.
160
164
headless (bool): Whether to run in headless mode.
161
165
"""
162
- LOG .info (' login started' )
166
+ LOG .info (" login started" )
163
167
164
168
# Check if the user is already authenticated
165
169
fail_if_authenticated (ctx , with_msg = MSG_FAIL_LOGIN_AUTHED )
@@ -168,26 +172,33 @@ def login(
168
172
169
173
info = None
170
174
171
- brief_msg : str = "Redirecting your browser to log in; once authenticated, " \
172
- "return here to start using Safety"
175
+ brief_msg : str = (
176
+ "Redirecting your browser to log in; once authenticated, "
177
+ "return here to start using Safety"
178
+ )
173
179
174
180
if ctx .obj .auth .org :
175
- console .print (f"Logging into [bold]{ ctx .obj .auth .org .name } [/bold] " \
176
- "organization." )
181
+ console .print (
182
+ f"Logging into [bold]{ ctx .obj .auth .org .name } [/bold] organization."
183
+ )
177
184
178
185
if headless :
179
186
brief_msg = "Running in headless mode. Please copy and open the following URL in a browser"
180
187
181
188
# Get authorization data and generate the authorization URL
182
- uri , initial_state = get_authorization_data (client = ctx .obj .auth .client ,
183
- code_verifier = ctx .obj .auth .code_verifier ,
184
- organization = ctx .obj .auth .org , headless = headless )
189
+ uri , initial_state = get_authorization_data (
190
+ client = ctx .obj .auth .client ,
191
+ code_verifier = ctx .obj .auth .code_verifier ,
192
+ organization = ctx .obj .auth .org ,
193
+ headless = headless ,
194
+ )
185
195
click .secho (brief_msg )
186
196
click .echo ()
187
197
188
198
# Process the browser callback to complete the authentication
189
- info = process_browser_callback (uri , initial_state = initial_state , ctx = ctx , headless = headless )
190
-
199
+ info = process_browser_callback (
200
+ uri , initial_state = initial_state , ctx = ctx , headless = headless
201
+ )
191
202
192
203
if info :
193
204
if info .get ("email" , None ):
@@ -198,50 +209,60 @@ def login(
198
209
if headless :
199
210
console .print ()
200
211
201
- render_successful_login (ctx .obj .auth , organization = organization )
202
-
203
212
initialize (ctx , refresh = True )
213
+ initialize_event_bus (ctx = ctx )
214
+ render_successful_login (ctx .obj .auth , organization = organization )
204
215
205
216
console .print ()
206
217
if ctx .obj .auth .org or ctx .obj .auth .email_verified :
207
218
console .print (
208
- "[tip]Tip[/tip]: now try [bold]`safety scan`[/bold] in your project’s root " \
209
- "folder to run a project scan or [bold]`safety -–help`[/bold] to learn more." )
219
+ "[tip]Tip[/tip]: now try [bold]`safety scan`[/bold] in your project’s root "
220
+ "folder to run a project scan or [bold]`safety -–help`[/bold] to learn more."
221
+ )
210
222
else :
211
- console .print (MSG_FINISH_REGISTRATION_TPL .format (email = ctx .obj .auth .email ))
223
+ console .print (
224
+ MSG_FINISH_REGISTRATION_TPL .format (email = ctx .obj .auth .email )
225
+ )
212
226
console .print ()
213
227
console .print (MSG_VERIFICATION_HINT )
214
228
else :
215
229
click .secho ("Safety is now authenticated but your email is missing." )
216
230
else :
217
231
msg = ":stop_sign: [red]"
218
232
if ctx .obj .auth .org :
219
- msg += f"Error logging into { ctx .obj .auth .org .name } organization " \
233
+ msg += (
234
+ f"Error logging into { ctx .obj .auth .org .name } organization "
220
235
f"with auth ID: { ctx .obj .auth .org .id } ."
236
+ )
221
237
else :
222
238
msg += "Error logging into Safety."
223
239
224
- msg += " Please try again, or use [bold]`safety auth -–help`[/bold] " \
240
+ msg += (
241
+ " Please try again, or use [bold]`safety auth -–help`[/bold] "
225
242
"for more information[/red]"
243
+ )
226
244
227
245
console .print (msg , emoji = True )
228
246
247
+
229
248
@auth_app .command (name = CMD_LOGOUT_NAME , help = CLI_AUTH_LOGOUT_HELP )
249
+ @handle_cmd_exception
250
+ @notify
230
251
def logout (ctx : typer .Context ) -> None :
231
252
"""
232
253
Log out of your current session.
233
254
234
255
Args:
235
256
ctx (typer.Context): The Typer context object.
236
257
"""
237
- LOG .info (' logout started' )
258
+ LOG .info (" logout started" )
238
259
239
- id_token = get_token (' id_token' )
260
+ id_token = get_token (" id_token" )
240
261
241
262
msg = MSG_NON_AUTHENTICATED
242
263
243
264
if id_token :
244
- # Clean the session if an ID token is found
265
+ # Clean the session if an ID token is found
245
266
if clean_session (ctx .obj .auth .client ):
246
267
msg = MSG_LOGOUT_DONE
247
268
else :
@@ -251,13 +272,23 @@ def logout(ctx: typer.Context) -> None:
251
272
252
273
253
274
@auth_app .command (name = CMD_STATUS_NAME , help = CLI_AUTH_STATUS_HELP )
254
- @click .option ("--ensure-auth/--no-ensure-auth" , default = False ,
255
- help = "This will keep running the command until an" \
256
- "authentication is made." )
257
- @click .option ("--login-timeout" , "-w" , type = int , default = 600 ,
258
- help = "Max time allowed to wait for an authentication." )
259
- def status (ctx : typer .Context , ensure_auth : bool = False ,
260
- login_timeout : int = 600 ) -> None :
275
+ @click .option (
276
+ "--ensure-auth/--no-ensure-auth" ,
277
+ default = False ,
278
+ help = "This will keep running the command until anauthentication is made." ,
279
+ )
280
+ @click .option (
281
+ "--login-timeout" ,
282
+ "-w" ,
283
+ type = int ,
284
+ default = 600 ,
285
+ help = "Max time allowed to wait for an authentication." ,
286
+ )
287
+ @handle_cmd_exception
288
+ @notify
289
+ def status (
290
+ ctx : typer .Context , ensure_auth : bool = False , login_timeout : int = 600
291
+ ) -> None :
261
292
"""
262
293
Display Safety CLI's current authentication status.
263
294
@@ -266,7 +297,7 @@ def status(ctx: typer.Context, ensure_auth: bool = False,
266
297
ensure_auth (bool): Whether to keep running until authentication is made.
267
298
login_timeout (int): Max time allowed to wait for authentication.
268
299
"""
269
- LOG .info (' status started' )
300
+ LOG .info (" status started" )
270
301
current_time = datetime .now ().strftime ("%Y-%m-%d %H:%M:%S" )
271
302
safety_version = get_version ()
272
303
console .print (f"[{ current_time } ]: Safety { safety_version } " )
@@ -283,20 +314,28 @@ def status(ctx: typer.Context, ensure_auth: bool = False,
283
314
verified = is_email_verified (info )
284
315
email_status = " [red](email not verified)[/red]" if not verified else ""
285
316
286
- console .print (f' [green]Authenticated as { info [" email" ]} [/green]{ email_status } ' )
317
+ console .print (f" [green]Authenticated as { info [' email' ]} [/green]{ email_status } " )
287
318
elif ensure_auth :
288
- console .print ('Safety is not authenticated. Launching default browser to log in' )
319
+ console .print (
320
+ "Safety is not authenticated. Launching default browser to log in"
321
+ )
289
322
console .print ()
290
- uri , initial_state = get_authorization_data (client = ctx .obj .auth .client ,
291
- code_verifier = ctx .obj .auth .code_verifier ,
292
- organization = ctx .obj .auth .org , ensure_auth = ensure_auth )
323
+ uri , initial_state = get_authorization_data (
324
+ client = ctx .obj .auth .client ,
325
+ code_verifier = ctx .obj .auth .code_verifier ,
326
+ organization = ctx .obj .auth .org ,
327
+ ensure_auth = ensure_auth ,
328
+ )
293
329
294
330
# Process the browser callback to complete the authentication
295
- info = process_browser_callback (uri , initial_state = initial_state ,
296
- timeout = login_timeout , ctx = ctx )
331
+ info = process_browser_callback (
332
+ uri , initial_state = initial_state , timeout = login_timeout , ctx = ctx
333
+ )
297
334
298
335
if not info :
299
- console .print (f'[red]Timeout error ({ login_timeout } seconds): not successfully authenticated without the timeout period.[/red]' )
336
+ console .print (
337
+ f"[red]Timeout error ({ login_timeout } seconds): not successfully authenticated without the timeout period.[/red]"
338
+ )
300
339
sys .exit (1 )
301
340
302
341
organization = None
@@ -311,33 +350,37 @@ def status(ctx: typer.Context, ensure_auth: bool = False,
311
350
312
351
313
352
@auth_app .command (name = CMD_REGISTER_NAME )
353
+ @handle_cmd_exception
354
+ @notify
314
355
def register (ctx : typer .Context ) -> None :
315
356
"""
316
357
Create a new user account for the safetycli.com service.
317
358
318
359
Args:
319
360
ctx (typer.Context): The Typer context object.
320
361
"""
321
- LOG .info (' register started' )
362
+ LOG .info (" register started" )
322
363
323
364
# Check if the user is already authenticated
324
365
fail_if_authenticated (ctx , with_msg = MSG_FAIL_REGISTER_AUTHED )
325
366
326
367
# Get authorization data and generate the registration URL
327
- uri , initial_state = get_authorization_data (client = ctx .obj .auth .client ,
328
- code_verifier = ctx .obj .auth .code_verifier ,
329
- sign_up = True )
330
-
331
- console .print ("Redirecting your browser to register for a free account. Once registered, return here to start using Safety." )
368
+ uri , initial_state = get_authorization_data (
369
+ client = ctx .obj .auth .client ,
370
+ code_verifier = ctx .obj .auth .code_verifier ,
371
+ sign_up = True ,
372
+ )
373
+
374
+ console .print (
375
+ "Redirecting your browser to register for a free account. Once registered, return here to start using Safety."
376
+ )
332
377
console .print ()
333
378
334
379
# Process the browser callback to complete the registration
335
- info = process_browser_callback (uri ,
336
- initial_state = initial_state , ctx = ctx )
380
+ info = process_browser_callback (uri , initial_state = initial_state , ctx = ctx )
337
381
338
382
if info :
339
- console .print (f' [green]Successfully registered { info .get (" email" )} [/green]' )
383
+ console .print (f" [green]Successfully registered { info .get (' email' )} [/green]" )
340
384
console .print ()
341
385
else :
342
- console .print ('[red]Unable to register in this time, try again.[/red]' )
343
-
386
+ console .print ("[red]Unable to register in this time, try again.[/red]" )
0 commit comments