GCC Code Coverage Report


src/
File: src/login/login_cmd.c
Date: 2026-03-31 17:48:22
Lines:
0/127
0.0%
Functions:
0/2
0.0%
Branches:
0/88
0.0%

Line Branch Exec Source
1 #include <glib.h>
2 #include <stdbool.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include <time.h>
6 #include "api/internal.h"
7 #include "config/config.h"
8 #include "login/login.h"
9
10 static error_t parse_opt(int key, char* arg, struct argp_state* state);
11
12 struct login_arguments {
13 struct global_arguments* global;
14 };
15
16 static struct argp_option options[] = {{NULL}};
17
18 static char doc[] =
19 "Login to GitTor. Prompts for email/username and password, then saves the "
20 "returned authentication token in the global config.";
21
22 static struct argp argp = {options, parse_opt, "", doc, NULL, NULL, NULL};
23
24 static error_t parse_opt(int key,
25 __attribute__((__unused__)) char* arg,
26 __attribute__((__unused__)) struct argp_state* state) {
27 switch (key) {
28 default:
29 return ARGP_ERR_UNKNOWN;
30 }
31
32 return 0;
33 }
34
35 extern int gittor_login(struct argp_state* state) {
36 // Set defaults arguments for login argumnets
37 struct login_arguments args = {0};
38
39 // Prepare arguments array for just login
40 int argc = state->argc - state->next + 1;
41 char** argv = &state->argv[state->next - 1];
42 args.global = state->input;
43
44 // Change the command name to gittor login
45 const char name[] = "login";
46 size_t argv0len = strlen(state->name) + sizeof(name) + 1;
47 char* argv0 = argv[0];
48 argv[0] = malloc(argv0len);
49 g_snprintf(argv[0], argv0len, "%s %s", state->name, name);
50
51 // Parse arguments
52 int err = argp_parse(&argp, argc, argv, ARGP_NO_EXIT, 0, &args);
53 if (err) {
54 free(argv[0]);
55 argv[0] = argv0;
56 state->next += argc - 1;
57
58 return err;
59 }
60
61 // Prompt for email/username
62 char identifier[256] = {0};
63 char password[73] = {0};
64
65 err =
66 prompt_line("Email or username", false, identifier, sizeof(identifier));
67 if (err) {
68 g_printerr("Email or username is required.\n");
69
70 free(argv[0]);
71 argv[0] = argv0;
72 state->next += argc - 1;
73
74 return err;
75 }
76
77 // Prompt for password
78 err = prompt_line("Password", true, password, sizeof(password));
79 if (err) {
80 g_printerr("Password is required.\n");
81
82 secure_zero(password, sizeof(password));
83 free(argv[0]);
84 argv[0] = argv0;
85 state->next += argc - 1;
86
87 return err;
88 }
89
90 // Build the login DTO and clear the password
91 login_dto_t login_dto = {0};
92 if (strchr(identifier, '@')) { // check if it is an email or username
93 login_dto.email = g_strdup(identifier);
94 login_dto.username = NULL;
95 } else {
96 login_dto.username = g_strdup(identifier);
97 login_dto.email = NULL;
98 }
99 login_dto.password = g_strdup(password);
100 secure_zero(password, sizeof(password));
101
102 CURL* curl = api_curl_handle_new();
103 if (!curl) {
104 login_dto_free(&login_dto);
105
106 free(argv[0]);
107 argv[0] = argv0;
108 state->next += argc - 1;
109
110 return EIO;
111 }
112
113 // Build the URL: /authenticate/login
114 char url[512] = {0};
115 if (api_build_url(url, sizeof(url), "/authenticate/login")) {
116 login_dto_free(&login_dto);
117 curl_easy_cleanup(curl);
118
119 free(argv[0]);
120 argv[0] = argv0;
121 state->next += argc - 1;
122
123 return EINVAL;
124 }
125
126 // Serialize the login struct to JSON
127 char* json_str = build_login_json(&login_dto);
128 if (!json_str) {
129 login_dto_free(&login_dto);
130 curl_easy_cleanup(curl);
131
132 free(argv[0]);
133 argv[0] = argv0;
134 state->next += argc - 1;
135
136 return EINVAL;
137 }
138
139 // Set up headers and response buffer
140 struct curl_slist* headers = NULL;
141 headers = curl_slist_append(headers, "Content-Type: application/json");
142 headers = curl_slist_append(headers, "Accept: application/json");
143
144 response_buf_t response = response_buf_init();
145
146 // Perform request
147 curl_easy_setopt(curl, CURLOPT_URL, url);
148 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
149 curl_easy_setopt(curl, CURLOPT_POST, 1L);
150 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_str);
151 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
152 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
153
154 CURLcode res = curl_easy_perform(curl);
155 api_result_e check = api_check_response(curl, res);
156
157 login_dto_free(&login_dto);
158 curl_slist_free_all(headers);
159 curl_easy_cleanup(curl);
160 g_free(json_str);
161
162 if (check != API_OK) {
163 if (check == API_BAD_REQUEST) {
164 g_printerr("Invalid request.\n");
165 } else if (check == API_FORBIDDEN) {
166 g_printerr("Invalid email/username or password.\n");
167 } else if (check == API_SERVER_ERR) {
168 g_printerr("Server error occurred. Please try again later.\n");
169 } else if (check == API_CURL_ERR) {
170 g_printerr("Network error occurred: %s\n", curl_easy_strerror(res));
171 } else {
172 g_printerr("An unknown error occurred.\n");
173 }
174
175 g_free(response.data);
176
177 free(argv[0]);
178 argv[0] = argv0;
179 state->next += argc - 1;
180
181 return EIO;
182 }
183
184 // Parse the returned AuthenticationDto JSON
185 authentication_dto_t* auth_dto = parse_authentication_json(response.data);
186 g_free(response.data);
187
188 // Persist the authentication data in the global config
189 config_id_t key_access_token = {.group = "auth", .key = "access_token"};
190 config_id_t key_token_type = {.group = "auth", .key = "token_type"};
191 config_id_t key_expires = {.group = "auth", .key = "expires"};
192
193 if (config_set(CONFIG_SCOPE_GLOBAL, &key_access_token,
194 auth_dto->access_token) ||
195 config_set(CONFIG_SCOPE_GLOBAL, &key_token_type,
196 auth_dto->token_type) ||
197 config_set(CONFIG_SCOPE_GLOBAL, &key_expires, auth_dto->expires)) {
198 authentication_dto_free(auth_dto);
199
200 free(argv[0]);
201 argv[0] = argv0;
202 state->next += argc - 1;
203
204 return EIO;
205 }
206
207 // Lock the config file permissions to be only accessible by the owner
208 char* config_path = global_config_path();
209 if (lock_file_permissions(config_path)) {
210 g_printerr(
211 "Warning: Failed to set config file permissions. Please ensure "
212 "that %s is only accessible by the owner.\n",
213 config_path);
214 }
215 g_free(config_path);
216
217 // Check if the token is already expired
218 time_t now = time(NULL);
219 time_t exp = 0;
220 if (parse_expiry_epoch(auth_dto->expires, &exp) == 0 && exp <= now) {
221 g_printerr(
222 "Warning: Received an already expired token. Please log in "
223 "again.\n");
224 err = EAGAIN;
225 } else {
226 g_printerr("Login successful.\n");
227 }
228
229 authentication_dto_free(auth_dto);
230
231 // Reset back to global
232 free(argv[0]);
233 argv[0] = argv0;
234 state->next += argc - 1;
235
236 return err;
237 }
238