Add special reply for reused TOTP tokens

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER 2023-11-06 11:35:26 +01:00
parent 28fe8dd499
commit 6e860f0c81
No known key found for this signature in database
GPG Key ID: 00135ACBD90B28DD
4 changed files with 63 additions and 21 deletions

View File

@ -198,7 +198,7 @@ static bool encode_uint8_t_array_to_base32(const uint8_t *in, const size_t in_le
}
static uint32_t last_code = 0;
bool verifyTOTP(const uint32_t incode)
enum totp_status verifyTOTP(const uint32_t incode)
{
// Decode base32 secret
uint8_t decoded_secret[RFC6238_SECRET_LEN];
@ -228,15 +228,15 @@ bool verifyTOTP(const uint32_t incode)
{
log_warn("2FA code has already been used (%i, %u), please wait %lu seconds",
i, gencode, (unsigned long)(RFC6238_X - (now % RFC6238_X)));
return false;
return TOTP_REUSED;
}
log_info("2FA code verified successfully at %i", i);
last_code = gencode;
return true;
return TOTP_CORRECT;
}
}
return false;
return TOTP_INVALID;
}
// Print TOTP code to stdout (for CLI use)

View File

@ -92,7 +92,12 @@ int api_auth_session_delete(struct ftl_conn *api);
bool is_local_api_user(const char *remote_addr) __attribute__((pure));
// 2FA methods
bool verifyTOTP(const uint32_t code);
enum totp_status {
TOTP_INVALID,
TOTP_CORRECT,
TOTP_REUSED,
} __attribute__ ((packed));
enum totp_status verifyTOTP(const uint32_t code);
int generateTOTP(struct ftl_conn *api);
int printTOTP(void);
int generateAppPw(struct ftl_conn *api);

View File

@ -524,13 +524,22 @@ int api_auth(struct ftl_conn *api)
NULL);
}
if(!verifyTOTP(json_totp->valueint))
enum totp_status totp = verifyTOTP(json_totp->valueint);
if(totp == TOTP_REUSED)
{
// 2FA token has been reused
return send_json_error(api, 401,
"unauthorized",
"Reused 2FA token",
"wait for new token");
}
else if(totp != TOTP_CORRECT)
{
// 2FA token is invalid
return send_json_error(api, 401,
"unauthorized",
"Invalid 2FA token",
NULL);
"unauthorized",
"Invalid 2FA token",
NULL);
}
}
@ -597,18 +606,18 @@ int api_auth(struct ftl_conn *api)
config.webserver.api.max_sessions.v.u16);
return send_json_error(api, 429,
"too_many_requests",
"Too many requests",
"no free API seats available");
"api_seats_exceeded",
"API seats exceeded",
"increase webserver.api.max_sessions");
}
}
else if(result == PASSWORD_RATE_LIMITED)
{
// Rate limited
return send_json_error(api, 429,
"too_many_requests",
"Too many requests",
"login rate limiting");
"rate_limiting",
"Rate-limiting login attempts",
NULL);
}
else
{

View File

@ -80,6 +80,8 @@ components:
$ref: 'auth.yaml#/components/examples/errors/no_password'
password_inval:
$ref: 'auth.yaml#/components/examples/errors/password_inval'
totp_missing:
$ref: 'auth.yaml#/components/examples/errors/totp_missing'
'401':
description: Unauthorized
content:
@ -88,6 +90,11 @@ components:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
examples:
totp_invalid:
$ref: 'auth.yaml#/components/examples/errors/totp_invalid'
totp_reused:
$ref: 'auth.yaml#/components/examples/errors/totp_reused'
'429':
description: Too Many Requests
content:
@ -491,6 +498,13 @@ components:
key: "bad_request"
message: "Field password has to be of type 'string'"
hint: null
totp_missing:
summary: Bad request (2FA token missing)
value:
error:
key: "bad_request"
message: "No 2FA token found in JSON payload"
hint: null
missing_session_id:
summary: Bad request (missing session ID)
value:
@ -512,20 +526,34 @@ components:
key: "bad_request"
message: "Session ID not in use"
hint: null
totp_invalid:
summary: 2FA token invalid
value:
error:
key: "unauthorized"
message: "Invalid 2FA token"
hint: null
totp_reused:
summary: 2FA token reused
value:
error:
key: "unauthorized"
message: "Reused 2FA token"
hint: "wait for new token"
rate_limit:
summary: Rate limit exceeded
value:
error:
key: "too_many_requests"
message: "Too many requests"
hint: "login rate limiting"
key: "rate_limiting"
message: "Rate-limiting login attempts"
hint: null
no_seats:
summary: No free API seats available
value:
error:
key: "too_many_requests"
message: "Too many requests"
hint: "no free API seats available"
key: "api_seats_exceeded"
message: "API seats exceeded"
hint: "increase webserver.api.max_sessions"
parameters:
id:
in: path