Limit OTP requests to one per expiry window. Closes #2000

This commit is contained in:
Gabe Kangas 2022-08-02 13:29:06 -07:00
parent c40eaa47e9
commit 0b5ddf433b
No known key found for this signature in database
GPG Key ID: 9A56337728BC81EA
3 changed files with 37 additions and 5 deletions

View File

@ -19,9 +19,19 @@ type OTPRegistration struct {
// to be active at a time. // to be active at a time.
var pendingAuthRequests = make(map[string]OTPRegistration) var pendingAuthRequests = make(map[string]OTPRegistration)
const registrationTimeout = time.Minute * 10
// RegisterFediverseOTP will start the OTP flow for a user, creating a new // RegisterFediverseOTP will start the OTP flow for a user, creating a new
// code and returning it to be sent to a destination. // code and returning it to be sent to a destination.
func RegisterFediverseOTP(accessToken, userID, userDisplayName, account string) OTPRegistration { func RegisterFediverseOTP(accessToken, userID, userDisplayName, account string) (OTPRegistration, bool) {
request, requestExists := pendingAuthRequests[accessToken]
// If a request is already registered and has not expired then return that
// existing request.
if requestExists && time.Since(request.Timestamp) < registrationTimeout {
return request, false
}
code, _ := createCode() code, _ := createCode()
r := OTPRegistration{ r := OTPRegistration{
Code: code, Code: code,
@ -32,14 +42,14 @@ func RegisterFediverseOTP(accessToken, userID, userDisplayName, account string)
} }
pendingAuthRequests[accessToken] = r pendingAuthRequests[accessToken] = r
return r return r, true
} }
// ValidateFediverseOTP will verify a OTP code for a auth request. // ValidateFediverseOTP will verify a OTP code for a auth request.
func ValidateFediverseOTP(accessToken, code string) (bool, *OTPRegistration) { func ValidateFediverseOTP(accessToken, code string) (bool, *OTPRegistration) {
request, ok := pendingAuthRequests[accessToken] request, ok := pendingAuthRequests[accessToken]
if !ok || request.Code != code || time.Since(request.Timestamp) > time.Minute*10 { if !ok || request.Code != code || time.Since(request.Timestamp) > registrationTimeout {
return false, nil return false, nil
} }

View File

@ -10,7 +10,11 @@ const (
) )
func TestOTPFlowValidation(t *testing.T) { func TestOTPFlowValidation(t *testing.T) {
r := RegisterFediverseOTP(accessToken, userID, userDisplayName, account) r, success := RegisterFediverseOTP(accessToken, userID, userDisplayName, account)
if !success {
t.Error("Registration should be permitted.")
}
if r.Code == "" { if r.Code == "" {
t.Error("Code is empty") t.Error("Code is empty")
@ -41,3 +45,16 @@ func TestOTPFlowValidation(t *testing.T) {
t.Error("UserDisplayName is not set correctly") t.Error("UserDisplayName is not set correctly")
} }
} }
func TestSingleOTPFlowRequest(t *testing.T) {
r1, _ := RegisterFediverseOTP(accessToken, userID, userDisplayName, account)
r2, s2 := RegisterFediverseOTP(accessToken, userID, userDisplayName, account)
if r1.Code != r2.Code {
t.Error("Only one registration should be permitted.")
}
if s2 {
t.Error("Second registration should not be permitted.")
}
}

View File

@ -29,7 +29,12 @@ func RegisterFediverseOTPRequest(u user.User, w http.ResponseWriter, r *http.Req
} }
accessToken := r.URL.Query().Get("accessToken") accessToken := r.URL.Query().Get("accessToken")
reg := fediverseauth.RegisterFediverseOTP(accessToken, u.ID, u.DisplayName, req.FediverseAccount) reg, success := fediverseauth.RegisterFediverseOTP(accessToken, u.ID, u.DisplayName, req.FediverseAccount)
if !success {
controllers.WriteSimpleResponse(w, false, "Could not register auth request. One may already be pending. Try again later.")
return
}
msg := fmt.Sprintf("<p>This is an automated message from %s. If you did not request this message please ignore or block. Your requested one-time code is:</p><p>%s</p>", data.GetServerName(), reg.Code) msg := fmt.Sprintf("<p>This is an automated message from %s. If you did not request this message please ignore or block. Your requested one-time code is:</p><p>%s</p>", data.GetServerName(), reg.Code)
if err := activitypub.SendDirectFederatedMessage(msg, reg.Account); err != nil { if err := activitypub.SendDirectFederatedMessage(msg, reg.Account); err != nil {
controllers.WriteSimpleResponse(w, false, "Could not send code to fediverse: "+err.Error()) controllers.WriteSimpleResponse(w, false, "Could not send code to fediverse: "+err.Error())