From db1c4901c78a3e178d5b51958ab07b4f5caee7b2 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Wed, 9 Dec 2020 17:25:09 +0100 Subject: [PATCH] Update IndieAuth (w/o PKCE for now) --- database.go | 5 +- databaseMigrations.go | 16 ++++ go.mod | 6 +- go.sum | 13 +-- http.go | 4 +- indieAuth.go | 8 +- indieAuthServer.go | 178 ++++++++++++++++--------------------- templates/indieauth.gohtml | 3 - 8 files changed, 108 insertions(+), 125 deletions(-) diff --git a/database.go b/database.go index ccbb2a2..cf1ddfd 100644 --- a/database.go +++ b/database.go @@ -56,7 +56,10 @@ func prepareAppDbStatement(query string) (*sql.Stmt, error) { dbStatementCacheMutex.Unlock() return stmt, nil }) - return stmt.(*sql.Stmt), err + if err != nil { + return nil, err + } + return stmt.(*sql.Stmt), nil } func appDbExec(query string, args ...interface{}) (sql.Result, error) { diff --git a/databaseMigrations.go b/databaseMigrations.go index 47dac90..8a2ed49 100644 --- a/databaseMigrations.go +++ b/databaseMigrations.go @@ -71,6 +71,22 @@ func migrateDb() error { return err }, }, + &migrator.Migration{ + Name: "00006", + Func: func(tx *sql.Tx) error { + _, err := tx.Exec(` + create table indieauthauthnew (time text not null, code text not null, client text not null, redirect text not null, scope text not null); + insert into indieauthauthnew (time, code, client, redirect, scope) select time, code, client, redirect, scope from indieauthauth; + drop table indieauthauth; + alter table indieauthauthnew rename to indieauthauth; + create table indieauthtokennew (time text not null, token text not null, client text not null, scope text not null); + insert into indieauthtokennew (time, token, client, scope) select time, token, client, scope from indieauthtoken; + drop table indieauthtoken; + alter table indieauthtokennew rename to indieauthtoken; + `) + return err + }, + }, ), ) if err != nil { diff --git a/go.mod b/go.mod index d82206c..1d8af08 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/pelletier/go-toml v1.8.1 // indirect github.com/smartystreets/assertions v1.2.0 // indirect github.com/snabb/sitemap v1.0.0 - github.com/spf13/afero v1.5.0 // indirect + github.com/spf13/afero v1.5.1 // indirect github.com/spf13/cast v1.3.1 github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/viper v1.7.1 @@ -57,12 +57,12 @@ require ( golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 // indirect golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect golang.org/x/mod v0.4.0 // indirect - golang.org/x/net v0.0.0-20201207224615-747e23833adb // indirect + golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 // indirect golang.org/x/sync v0.0.0-20201207232520-09787c993a3a golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d // indirect golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b // indirect golang.org/x/text v0.3.4 // indirect - golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7 // indirect + golang.org/x/tools v0.0.0-20201208233053-a543418bbed2 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 70f8745..9f935f0 100644 --- a/go.sum +++ b/go.sum @@ -287,8 +287,8 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.5.0 h1:8Wb647pxgVlypPIdcDlffCLCHCElBZ1sCF6i85qNvRw= -github.com/spf13/afero v1.5.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg= +github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= @@ -418,8 +418,8 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201207224615-747e23833adb h1:xj2oMIbduz83x7tzglytWT7spn6rP+9hvKjTpro6/pM= -golang.org/x/net v0.0.0-20201207224615-747e23833adb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -460,6 +460,7 @@ golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d h1:MiWWjyhUzZ+jvhZvloX6ZrUsd golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b h1:a0ErnNnPKmhDyIXQvdZr+Lq8dc8xpMeqkF8y5PgQU4Q= golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -496,8 +497,8 @@ golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnf golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7 h1:2OSu5vYyX4LVqZAtqZXnFEcN26SDKIJYlEVIRl1tj8U= -golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2 h1:vEtypaVub6UvKkiXZ2xx9QIvp9TL7sI7xp7vdi2kezA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= diff --git a/http.go b/http.go index c1444b1..81a2c06 100644 --- a/http.go +++ b/http.go @@ -109,9 +109,9 @@ func buildHandler() (http.Handler, error) { // IndieAuth r.Route("/indieauth", func(indieauthRouter chi.Router) { indieauthRouter.Use(middleware.NoCache) - indieauthRouter.With(authMiddleware, minifier.Middleware).Get("/", indieAuthAuthGet) + indieauthRouter.With(minifier.Middleware).Get("/", indieAuthRequest) indieauthRouter.With(authMiddleware).Post("/accept", indieAuthAccept) - indieauthRouter.Post("/", indieAuthAuthPost) + indieauthRouter.Post("/", indieAuthVerification) indieauthRouter.Get("/token", indieAuthToken) indieauthRouter.Post("/token", indieAuthToken) }) diff --git a/indieAuth.go b/indieAuth.go index 6c0bde2..08e1fd7 100644 --- a/indieAuth.go +++ b/indieAuth.go @@ -3,7 +3,6 @@ package main import ( "context" "net/http" - "net/http/httptest" "strings" ) @@ -18,12 +17,7 @@ func checkIndieAuth(next http.Handler) http.Handler { http.Error(w, err.Error(), http.StatusUnauthorized) return } - if !isAllowedHost(httptest.NewRequest(http.MethodGet, tokenData.Me, nil), appConfig.Server.Domain) { - http.Error(w, "Forbidden", http.StatusUnauthorized) - return - } - ctx := context.WithValue(r.Context(), "scope", strings.Join(tokenData.Scopes, " ")) - next.ServeHTTP(w, r.WithContext(ctx)) + next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "scope", strings.Join(tokenData.Scopes, " ")))) return }) } diff --git a/indieAuthServer.go b/indieAuthServer.go index 9b892bf..5394e54 100644 --- a/indieAuthServer.go +++ b/indieAuthServer.go @@ -14,85 +14,46 @@ import ( ) // https://www.w3.org/TR/indieauth/ +// https://indieauth.spec.indieweb.org/ type indieAuthData struct { - Me string - ClientID string - RedirectURI string - State string - ResponseType string - Scopes []string - code string - token string - time time.Time + ClientID string + RedirectURI string + State string + Scopes []string + code string + token string + time time.Time } -func indieAuthAuthGet(w http.ResponseWriter, r *http.Request) { - // Authentication / authorization request +func indieAuthRequest(w http.ResponseWriter, r *http.Request) { + // Authorization request r.ParseForm() data := &indieAuthData{ - Me: r.Form.Get("me"), - ClientID: r.Form.Get("client_id"), - RedirectURI: r.Form.Get("redirect_uri"), - State: r.Form.Get("state"), - ResponseType: r.Form.Get("response_type"), + ClientID: r.Form.Get("client_id"), + RedirectURI: r.Form.Get("redirect_uri"), + State: r.Form.Get("state"), } - if data.ResponseType == "" { - data.ResponseType = "id" + if rt := r.Form.Get("response_type"); rt != "code" && rt != "id" && rt != "" { + http.Error(w, "response_type must be code", http.StatusBadRequest) + return } if scope := r.Form.Get("scope"); scope != "" { data.Scopes = strings.Split(scope, " ") } - if !isValidProfileURL(data.Me) || !isValidProfileURL(data.ClientID) || !isValidProfileURL(data.RedirectURI) { - http.Error(w, "me, client_id and redirect_uri need to by valid URLs", http.StatusBadRequest) + if !isValidProfileURL(data.ClientID) || !isValidProfileURL(data.RedirectURI) { + http.Error(w, "client_id and redirect_uri need to by valid URLs", http.StatusBadRequest) return } if data.State == "" { http.Error(w, "state must not be empty", http.StatusBadRequest) return } - if data.ResponseType != "id" && data.ResponseType != "code" { - http.Error(w, "response_type must be empty or id or code", http.StatusBadRequest) - return - } - // if data.ResponseType == "code" && len(data.Scopes) < 1 { - // http.Error(w, "scope is missing or empty", http.StatusBadRequest) - // return - // } render(w, "indieauth", &renderData{ Data: data, }) } -func indieAuthAuthPost(w http.ResponseWriter, r *http.Request) { - // Authentication verification - r.ParseForm() - data := &indieAuthData{ - code: r.Form.Get("code"), - ClientID: r.Form.Get("client_id"), - RedirectURI: r.Form.Get("redirect_uri"), - } - valid, err := data.verifyAuthorization(true) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if !valid { - http.Error(w, "Authentication not valid", http.StatusForbidden) - return - } - res := &tokenResponse{ - Me: data.Me, - } - w.Header().Add(contentType, contentTypeJSONUTF8) - err = json.NewEncoder(w).Encode(res) - if err != nil { - w.Header().Del(contentType) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } -} - func isValidProfileURL(profileURL string) bool { u, err := url.Parse(profileURL) if err != nil { @@ -101,8 +62,6 @@ func isValidProfileURL(profileURL string) bool { if u.Scheme != "http" && u.Scheme != "https" { return false } - // Missing: Check path - // Missing: Check single/double dot path if u.Fragment != "" { return false } @@ -120,16 +79,14 @@ func indieAuthAccept(w http.ResponseWriter, r *http.Request) { // Authentication flow r.ParseForm() data := &indieAuthData{ - Me: r.Form.Get("me"), - ClientID: r.Form.Get("client_id"), - RedirectURI: r.Form.Get("redirect_uri"), - State: r.Form.Get("state"), - ResponseType: r.Form.Get("response_type"), - Scopes: r.Form["scopes"], - time: time.Now(), + ClientID: r.Form.Get("client_id"), + RedirectURI: r.Form.Get("redirect_uri"), + State: r.Form.Get("state"), + Scopes: r.Form["scopes"], + time: time.Now(), } sha := sha1.New() - sha.Write([]byte(data.time.String() + data.Me + data.ClientID)) + sha.Write([]byte(data.time.String() + data.ClientID)) data.code = fmt.Sprintf("%x", sha.Sum(nil)) err := data.saveAuthorization() if err != nil { @@ -147,6 +104,35 @@ type tokenResponse struct { ClientID string `json:"client_id,omitempty"` } +func indieAuthVerification(w http.ResponseWriter, r *http.Request) { + // Authorization verification + r.ParseForm() + data := &indieAuthData{ + code: r.Form.Get("code"), + ClientID: r.Form.Get("client_id"), + RedirectURI: r.Form.Get("redirect_uri"), + } + valid, err := data.verifyAuthorization() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if !valid { + http.Error(w, "Authentication not valid", http.StatusForbidden) + return + } + res := &tokenResponse{ + Me: appConfig.Server.PublicAddress, + } + w.Header().Add(contentType, contentTypeJSONUTF8) + err = json.NewEncoder(w).Encode(res) + if err != nil { + w.Header().Del(contentType) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + func indieAuthToken(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { // Token verification @@ -157,7 +143,7 @@ func indieAuthToken(w http.ResponseWriter, r *http.Request) { } res := &tokenResponse{ Scope: strings.Join(data.Scopes, " "), - Me: data.Me, + Me: appConfig.Server.PublicAddress, ClientID: data.ClientID, } w.Header().Add(contentType, contentTypeJSONUTF8) @@ -182,9 +168,8 @@ func indieAuthToken(w http.ResponseWriter, r *http.Request) { code: r.Form.Get("code"), ClientID: r.Form.Get("client_id"), RedirectURI: r.Form.Get("redirect_uri"), - Me: r.Form.Get("me"), } - valid, err := data.verifyAuthorization(false) + valid, err := data.verifyAuthorization() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -199,7 +184,7 @@ func indieAuthToken(w http.ResponseWriter, r *http.Request) { } data.time = time.Now() sha := sha1.New() - sha.Write([]byte(data.time.String() + data.Me + data.ClientID)) + sha.Write([]byte(data.time.String() + data.ClientID)) data.token = fmt.Sprintf("%x", sha.Sum(nil)) err = data.saveToken() if err != nil { @@ -210,7 +195,7 @@ func indieAuthToken(w http.ResponseWriter, r *http.Request) { TokenType: "Bearer", AccessToken: data.token, Scope: strings.Join(data.Scopes, " "), - Me: data.Me, + Me: appConfig.Server.PublicAddress, } w.Header().Add(contentType, contentTypeJSONUTF8) err = json.NewEncoder(w).Encode(res) @@ -227,38 +212,25 @@ func indieAuthToken(w http.ResponseWriter, r *http.Request) { } func (data *indieAuthData) saveAuthorization() (err error) { - _, err = appDbExec("insert into indieauthauth (time, code, me, client, redirect, scope) values (?, ?, ?, ?, ?, ?)", data.time.Unix(), data.code, data.Me, data.ClientID, data.RedirectURI, strings.Join(data.Scopes, " ")) + _, err = appDbExec("insert into indieauthauth (time, code, client, redirect, scope) values (?, ?, ?, ?, ?)", data.time.Unix(), data.code, data.ClientID, data.RedirectURI, strings.Join(data.Scopes, " ")) return } -func (data *indieAuthData) verifyAuthorization(authentication bool) (valid bool, err error) { +func (data *indieAuthData) verifyAuthorization() (valid bool, err error) { // code valid for 600 seconds - if !authentication { - row, err := appDbQueryRow("select code, me, client, redirect, scope from indieauthauth where time >= ? and code = ? and me = ? and client = ? and redirect = ?", time.Now().Unix()-600, data.code, data.Me, data.ClientID, data.RedirectURI) - if err != nil { - return false, err - } - scope := "" - err = row.Scan(&data.code, &data.Me, &data.ClientID, &data.RedirectURI, &scope) - if err == sql.ErrNoRows { - return false, nil - } else if err != nil { - return false, err - } - if scope != "" { - data.Scopes = strings.Split(scope, " ") - } - } else { - row, err := appDbQueryRow("select code, me, client, redirect from indieauthauth where time >= ? and code = ? and client = ? and redirect = ?", time.Now().Unix()-600, data.code, data.ClientID, data.RedirectURI) - if err != nil { - return false, err - } - err = row.Scan(&data.code, &data.Me, &data.ClientID, &data.RedirectURI) - if err == sql.ErrNoRows { - return false, nil - } else if err != nil { - return false, err - } + row, err := appDbQueryRow("select code, client, redirect, scope from indieauthauth where time >= ? and code = ? and client = ? and redirect = ?", time.Now().Unix()-600, data.code, data.ClientID, data.RedirectURI) + if err != nil { + return false, err + } + scope := "" + err = row.Scan(&data.code, &data.ClientID, &data.RedirectURI, &scope) + if err == sql.ErrNoRows { + return false, nil + } else if err != nil { + return false, err + } + if scope != "" { + data.Scopes = strings.Split(scope, " ") } valid = true _, err = appDbExec("delete from indieauthauth where code = ? or time < ?", data.code, time.Now().Unix()-600) @@ -267,7 +239,7 @@ func (data *indieAuthData) verifyAuthorization(authentication bool) (valid bool, } func (data *indieAuthData) saveToken() (err error) { - _, err = appDbExec("insert into indieauthtoken (time, token, me, client, scope) values (?, ?, ?, ?, ?)", data.time.Unix(), data.token, data.Me, data.ClientID, strings.Join(data.Scopes, " ")) + _, err = appDbExec("insert into indieauthtoken (time, token, client, scope) values (?, ?, ?, ?)", data.time.Unix(), data.token, data.ClientID, strings.Join(data.Scopes, " ")) return } @@ -276,13 +248,13 @@ func verifyIndieAuthToken(token string) (data *indieAuthData, err error) { data = &indieAuthData{ Scopes: []string{}, } - row, err := appDbQueryRow("select time, token, me, client, scope from indieauthtoken where token = @token", sql.Named("token", token)) + row, err := appDbQueryRow("select time, token, client, scope from indieauthtoken where token = @token", sql.Named("token", token)) if err != nil { return nil, err } timeString := "" scope := "" - err = row.Scan(&timeString, &data.token, &data.Me, &data.ClientID, &scope) + err = row.Scan(&timeString, &data.token, &data.ClientID, &scope) if err == sql.ErrNoRows { return nil, errors.New("token not found") } else if err != nil { diff --git a/templates/indieauth.gohtml b/templates/indieauth.gohtml index 2502a42..83d02f1 100644 --- a/templates/indieauth.gohtml +++ b/templates/indieauth.gohtml @@ -15,14 +15,11 @@ {{ end }} {{ end }} -

me: {{ .Data.Me }}

client_id: {{ .Data.ClientID }}

redirect_uri: {{ .Data.RedirectURI }}

- -