spotify_cli/cli/commands/
auth.rs1use super::{init_token_store, load_config};
2use crate::io::output::{ErrorKind, Response};
3use crate::oauth::flow::OAuthFlow;
4use crate::{auth_error, storage_error};
5use tracing::{debug, warn};
6
7pub async fn auth_login(force: bool) -> Response {
8 debug!(force = force, "Starting login flow");
9
10 let config = match load_config() {
11 Ok(c) => c,
12 Err(e) => return e,
13 };
14
15 let token_store = match init_token_store() {
16 Ok(s) => s,
17 Err(e) => return e,
18 };
19
20 if !force && let Ok(token) = token_store.load() {
21 debug!(expired = token.is_expired(), "Found existing token");
22 if !token.is_expired() {
23 return Response::success_with_payload(
24 200,
25 "Already logged in",
26 serde_json::json!({
27 "expires_in": token.seconds_until_expiry()
28 }),
29 );
30 }
31
32 if let Some(refresh_token) = &token.refresh_token {
33 let flow = OAuthFlow::new(config.client_id().to_string());
34 match flow.refresh(refresh_token).await {
35 Ok(new_token) => {
36 if let Err(e) = token_store.save(&new_token) {
37 return storage_error!("Failed to save token", e);
38 }
39 return Response::success_with_payload(
40 200,
41 "Token refreshed",
42 serde_json::json!({
43 "expires_in": new_token.seconds_until_expiry()
44 }),
45 );
46 }
47 Err(e) => {
48 warn!(error = %e, "Token refresh failed, opening browser login");
49 }
50 }
51 }
52 }
53
54 let flow = OAuthFlow::new(config.client_id().to_string());
55 match flow.authenticate().await {
56 Ok(token) => {
57 if let Err(e) = token_store.save(&token) {
58 return storage_error!("Failed to save token", e);
59 }
60 Response::success_with_payload(
61 200,
62 "Login successful",
63 serde_json::json!({
64 "expires_in": token.seconds_until_expiry()
65 }),
66 )
67 }
68 Err(e) => auth_error!("Login failed", e),
69 }
70}
71
72pub async fn auth_logout() -> Response {
73 let token_store = match init_token_store() {
74 Ok(s) => s,
75 Err(e) => return e,
76 };
77
78 if !token_store.exists() {
79 return Response::success(200, "Already logged out");
80 }
81
82 match token_store.delete() {
83 Ok(_) => Response::success(200, "Logged out successfully"),
84 Err(e) => storage_error!("Failed to delete token", e),
85 }
86}
87
88pub async fn auth_refresh() -> Response {
89 let config = match load_config() {
90 Ok(c) => c,
91 Err(e) => return e,
92 };
93
94 let token_store = match init_token_store() {
95 Ok(s) => s,
96 Err(e) => return e,
97 };
98
99 let token = match token_store.load() {
100 Ok(t) => t,
101 Err(_) => {
102 return Response::err(
103 401,
104 "Not logged in. Run: spotify-cli auth login",
105 ErrorKind::Auth,
106 );
107 }
108 };
109
110 let refresh_token = match &token.refresh_token {
111 Some(t) => t,
112 None => return Response::err(401, "No refresh token available", ErrorKind::Auth),
113 };
114
115 let flow = OAuthFlow::new(config.client_id().to_string());
116 match flow.refresh(refresh_token).await {
117 Ok(new_token) => {
118 if let Err(e) = token_store.save(&new_token) {
119 return storage_error!("Failed to save token", e);
120 }
121 Response::success_with_payload(
122 200,
123 "Token refreshed",
124 serde_json::json!({
125 "expires_in": new_token.seconds_until_expiry()
126 }),
127 )
128 }
129 Err(e) => auth_error!("Failed to refresh token", e),
130 }
131}
132
133pub async fn auth_status() -> Response {
134 let token_store = match init_token_store() {
135 Ok(s) => s,
136 Err(e) => return e,
137 };
138
139 if !token_store.exists() {
140 return Response::success_with_payload(
141 200,
142 "Not authenticated",
143 serde_json::json!({
144 "authenticated": false
145 }),
146 );
147 }
148
149 match token_store.load() {
150 Ok(token) => {
151 let expired = token.is_expired();
152 let expires_in = token.seconds_until_expiry();
153
154 Response::success_with_payload(
155 200,
156 if expired {
157 "Token expired"
158 } else {
159 "Authenticated"
160 },
161 serde_json::json!({
162 "authenticated": !expired,
163 "expired": expired,
164 "expires_in": expires_in
165 }),
166 )
167 }
168 Err(e) => storage_error!("Failed to load token", e),
169 }
170}