mirror of
https://github.com/owncast/owncast.git
synced 2025-11-01 19:32:20 +08:00
* 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 * don't redirect unless a URL is present avoids redirecting to `undefined` if there was an error * improve error message if owncast server URL isn't set * fix IndieAuth PKCE implementation use SHA256 instead of SHA1, generates a longer code verifier (must be 43-128 chars long), fixes URL-safe SHA256 encoding * return real profile data for IndieAuth response * check the code verifier in the IndieAuth server * Linting * Add new chat settings modal anad split up indieauth ui * Remove logging error * Update the IndieAuth modal UI. For #1273 * Add IndieAuth repsonse error checking * Disable IndieAuth client if server URL is not set. * Add explicit error messages for specific error types * Fix bad logic * Return OAuth-keyed error responses for indieauth server * Display IndieAuth error in plain text with link to return to main page * Remove redundant check * Add additional detail to error * Hide IndieAuth details behind disclosure details * Break out migration into two steps because some people have been runing dev in production * Add auth option to user dropdown Co-authored-by: Aaron Parecki <aaron@parecki.com>
238 lines
5.4 KiB
Go
238 lines
5.4 KiB
Go
package data
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/owncast/owncast/utils"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/teris-io/shortid"
|
|
)
|
|
|
|
func migrateToSchema5(db *sql.DB) {
|
|
// Access tokens have been broken into its own table.
|
|
|
|
// Authenticated bool added to the users table.
|
|
stmt, err := db.Prepare("ALTER TABLE users ADD authenticated_at timestamp DEFAULT null ")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer stmt.Close()
|
|
_, err = stmt.Exec()
|
|
if err != nil {
|
|
log.Warnln(err)
|
|
}
|
|
|
|
// Migrate the access tokens from the users table to the access tokens table.
|
|
query := `SELECT id, access_token, created_at FROM users`
|
|
rows, err := db.Query(query)
|
|
if err != nil || rows.Err() != nil {
|
|
log.Errorln("error migrating access tokens to schema v5", err, rows.Err())
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
valueStrings := []string{}
|
|
valueArgs := []interface{}{}
|
|
|
|
var token string
|
|
var userID string
|
|
var timestamp time.Time
|
|
for rows.Next() {
|
|
if err := rows.Scan(&userID, &token, ×tamp); err != nil {
|
|
log.Error("There is a problem reading the database.", err)
|
|
return
|
|
}
|
|
|
|
valueStrings = append(valueStrings, "(?, ?, ?)")
|
|
valueArgs = append(valueArgs, userID, token, timestamp)
|
|
}
|
|
|
|
smt := `INSERT INTO user_access_tokens(token, user_id, timestamp) VALUES %s ON CONFLICT DO NOTHING`
|
|
smt = fmt.Sprintf(smt, strings.Join(valueStrings, ","))
|
|
// fmt.Println(smt)
|
|
tx, err := db.Begin()
|
|
if err != nil {
|
|
log.Fatalln("Error starting transaction", err)
|
|
}
|
|
_, err = tx.Exec(smt, valueArgs...)
|
|
if err != nil {
|
|
_ = tx.Rollback()
|
|
log.Fatalln("Error inserting access tokens", err)
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
log.Fatalln("Error committing transaction", err)
|
|
}
|
|
|
|
// Remove old access token column from the users table.
|
|
stmt, err = db.Prepare("ALTER TABLE users DROP COLUMN access_token;")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer stmt.Close()
|
|
_, err = stmt.Exec()
|
|
if err != nil {
|
|
log.Warnln(err)
|
|
}
|
|
}
|
|
|
|
func migrateToSchema4(db *sql.DB) {
|
|
// Access tokens have been broken into its own table.
|
|
stmt, err := db.Prepare("ALTER TABLE ap_followers ADD COLUMN request_object BLOB")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer stmt.Close()
|
|
_, err = stmt.Exec()
|
|
if err != nil {
|
|
log.Warnln(err)
|
|
}
|
|
}
|
|
|
|
func migrateToSchema3(db *sql.DB) {
|
|
// Since it's just a backlog of chat messages let's wipe the old messages
|
|
// and recreate the table.
|
|
|
|
// Drop the old messages table
|
|
stmt, err := db.Prepare("DROP TABLE messages")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer stmt.Close()
|
|
_, err = stmt.Exec()
|
|
if err != nil {
|
|
log.Warnln(err)
|
|
}
|
|
|
|
// Recreate it
|
|
CreateMessagesTable(db)
|
|
}
|
|
|
|
func migrateToSchema2(db *sql.DB) {
|
|
// Since it's just a backlog of chat messages let's wipe the old messages
|
|
// and recreate the table.
|
|
|
|
// Drop the old messages table
|
|
stmt, err := db.Prepare("DROP TABLE messages")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer stmt.Close()
|
|
_, err = stmt.Exec()
|
|
if err != nil {
|
|
log.Warnln(err)
|
|
}
|
|
|
|
// Recreate it
|
|
CreateMessagesTable(db)
|
|
}
|
|
|
|
func migrateToSchema1(db *sql.DB) {
|
|
// Since it's just a backlog of chat messages let's wipe the old messages
|
|
// and recreate the table.
|
|
|
|
// Drop the old messages table
|
|
stmt, err := db.Prepare("DROP TABLE messages")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer stmt.Close()
|
|
_, err = stmt.Exec()
|
|
if err != nil {
|
|
log.Warnln(err)
|
|
}
|
|
|
|
// Recreate it
|
|
CreateMessagesTable(db)
|
|
|
|
// Migrate access tokens to become chat users
|
|
type oldAccessToken struct {
|
|
accessToken string
|
|
displayName string
|
|
scopes string
|
|
createdAt time.Time
|
|
lastUsedAt *time.Time
|
|
}
|
|
|
|
oldAccessTokens := make([]oldAccessToken, 0)
|
|
|
|
query := `SELECT * FROM access_tokens`
|
|
|
|
rows, err := db.Query(query)
|
|
if err != nil || rows.Err() != nil {
|
|
log.Errorln("error migrating access tokens to schema v1", err, rows.Err())
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
var token string
|
|
var name string
|
|
var scopes string
|
|
var timestampString string
|
|
var lastUsedString *string
|
|
|
|
if err := rows.Scan(&token, &name, &scopes, ×tampString, &lastUsedString); err != nil {
|
|
log.Error("There is a problem reading the database.", err)
|
|
return
|
|
}
|
|
|
|
timestamp, err := time.Parse(time.RFC3339, timestampString)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var lastUsed *time.Time
|
|
if lastUsedString != nil {
|
|
lastUsedTime, _ := time.Parse(time.RFC3339, *lastUsedString)
|
|
lastUsed = &lastUsedTime
|
|
}
|
|
|
|
oldToken := oldAccessToken{
|
|
accessToken: token,
|
|
displayName: name,
|
|
scopes: scopes,
|
|
createdAt: timestamp,
|
|
lastUsedAt: lastUsed,
|
|
}
|
|
|
|
oldAccessTokens = append(oldAccessTokens, oldToken)
|
|
}
|
|
|
|
// Recreate them as users
|
|
for _, token := range oldAccessTokens {
|
|
color := utils.GenerateRandomDisplayColor()
|
|
if err := insertAPIToken(db, token.accessToken, token.displayName, color, token.scopes); err != nil {
|
|
log.Errorln("Error migrating access token", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func insertAPIToken(db *sql.DB, token string, name string, color int, scopes string) error {
|
|
log.Debugln("Adding new access token:", name)
|
|
|
|
id := shortid.MustGenerate()
|
|
|
|
tx, err := db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stmt, err := tx.Prepare("INSERT INTO users(id, access_token, display_name, display_color, scopes, type) values(?, ?, ?, ?, ?, ?)")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
if _, err = stmt.Exec(id, token, name, color, scopes, "API"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|