Add special reply for reused TOTP tokens
Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
parent
28fe8dd499
commit
6e860f0c81
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue