Fediverse-based authentication (#1846)

* Able to authenticate user against IndieAuth. For #1273

* WIP server indieauth endpoint. For https://github.com/owncast/owncast/issues/1272

* Add migration to remove access tokens from user

* Add authenticated bool to user for display purposes

* Add indieauth modal and auth flair to display names. For #1273

* Validate URLs and display errors

* Renames, cleanups

* Handle relative auth endpoint paths. Add error handling for missing redirects.

* Disallow using display names in use by registered users. Closes #1810

* Verify code verifier via code challenge on callback

* Use relative path to authorization_endpoint

* Post-rebase fixes

* Use a timestamp instead of a bool for authenticated

* Propertly handle and display error in modal

* Use auth'ed timestamp to derive authenticated flag to display in chat

* Fediverse chat auth via OTP

* Increase validity time just in case

* Add fediverse auth into auth modal

* Text, validation, cleanup updates for fedi auth

* Fix typo

* Remove unused images

* Remove unused file

* Add chat display name to auth modal text
This commit is contained in:
Gabe Kangas
2022-04-22 17:23:14 -07:00
committed by GitHub
parent 8b7e2b945e
commit a082cf3a77
21 changed files with 855 additions and 81 deletions

View File

@@ -4,8 +4,8 @@ package auth
type Type string
// The different auth types we support.
// Currently only IndieAuth.
const (
// IndieAuth https://indieauth.spec.indieweb.org/.
IndieAuth Type = "indieauth"
Fediverse Type = "fediverse"
)

View File

@@ -0,0 +1,63 @@
package fediverse
import (
"crypto/rand"
"io"
"time"
)
// OTPRegistration represents a single OTP request.
type OTPRegistration struct {
UserID string
UserDisplayName string
Code string
Account string
Timestamp time.Time
}
// Key by access token to limit one OTP request for a person
// to be active at a time.
var pendingAuthRequests = make(map[string]OTPRegistration)
// RegisterFediverseOTP will start the OTP flow for a user, creating a new
// code and returning it to be sent to a destination.
func RegisterFediverseOTP(accessToken, userID, userDisplayName, account string) OTPRegistration {
code, _ := createCode()
r := OTPRegistration{
Code: code,
UserID: userID,
UserDisplayName: userDisplayName,
Account: account,
Timestamp: time.Now(),
}
pendingAuthRequests[accessToken] = r
return r
}
// ValidateFediverseOTP will verify a OTP code for a auth request.
func ValidateFediverseOTP(accessToken, code string) (bool, *OTPRegistration) {
request, ok := pendingAuthRequests[accessToken]
if !ok || request.Code != code || time.Since(request.Timestamp) > time.Minute*10 {
return false, nil
}
delete(pendingAuthRequests, accessToken)
return true, &request
}
func createCode() (string, error) {
table := [...]byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
digits := 6
b := make([]byte, digits)
n, err := io.ReadAtLeast(rand.Reader, b, digits)
if n != digits {
return "", err
}
for i := 0; i < len(b); i++ {
b[i] = table[int(b[i])%len(table)]
}
return string(b), nil
}

View File

@@ -0,0 +1,43 @@
package fediverse
import "testing"
const (
accessToken = "fake-access-token"
account = "blah"
userID = "fake-user-id"
userDisplayName = "fake-user-display-name"
)
func TestOTPFlowValidation(t *testing.T) {
r := RegisterFediverseOTP(accessToken, userID, userDisplayName, account)
if r.Code == "" {
t.Error("Code is empty")
}
if r.Account != account {
t.Error("Account is not set correctly")
}
if r.Timestamp.IsZero() {
t.Error("Timestamp is empty")
}
valid, registration := ValidateFediverseOTP(accessToken, r.Code)
if !valid {
t.Error("Code is not valid")
}
if registration.Account != account {
t.Error("Account is not set correctly")
}
if registration.UserID != userID {
t.Error("UserID is not set correctly")
}
if registration.UserDisplayName != userDisplayName {
t.Error("UserDisplayName is not set correctly")
}
}

View File

@@ -55,6 +55,7 @@ func GetUserByAuth(authToken string, authType Type) *user.User {
Type: string(authType),
})
if err != nil {
log.Errorln(err)
return nil
}