Remove extra column from users table (HSTORE field)
Migrated key/value pairs to specific columns.
This commit is contained in:
parent
ae74f94655
commit
83f3ccab0e
19 changed files with 256 additions and 141 deletions
|
@ -18,17 +18,22 @@ const (
|
||||||
|
|
||||||
// User represents a user in the system.
|
// User represents a user in the system.
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
IsAdmin bool `json:"is_admin"`
|
IsAdmin bool `json:"is_admin"`
|
||||||
Theme string `json:"theme"`
|
Theme string `json:"theme"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
Timezone string `json:"timezone"`
|
Timezone string `json:"timezone"`
|
||||||
EntryDirection string `json:"entry_sorting_direction"`
|
EntryDirection string `json:"entry_sorting_direction"`
|
||||||
EntriesPerPage int `json:"entries_per_page"`
|
Stylesheet string `json:"stylesheet"`
|
||||||
LastLoginAt *time.Time `json:"last_login_at"`
|
GoogleID string `json:"google_id"`
|
||||||
Extra map[string]string `json:"extra"`
|
OpenIDConnectID string `json:"openid_connect_id"`
|
||||||
|
EntriesPerPage int `json:"entries_per_page"`
|
||||||
|
KeyboardShortcuts bool `json:"keyboard_shortcuts"`
|
||||||
|
ShowReadingTime bool `json:"show_reading_time"`
|
||||||
|
EntrySwipe bool `json:"entry_swipe"`
|
||||||
|
LastLoginAt *time.Time `json:"last_login_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u User) String() string {
|
func (u User) String() string {
|
||||||
|
|
|
@ -4,7 +4,9 @@
|
||||||
|
|
||||||
package database // import "miniflux.app/database"
|
package database // import "miniflux.app/database"
|
||||||
|
|
||||||
import "database/sql"
|
import (
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
var schemaVersion = len(migrations)
|
var schemaVersion = len(migrations)
|
||||||
|
|
||||||
|
@ -427,4 +429,71 @@ var migrations = []func(tx *sql.Tx) error{
|
||||||
_, err = tx.Exec(sql)
|
_, err = tx.Exec(sql)
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
|
func(tx *sql.Tx) (err error) {
|
||||||
|
_, err = tx.Exec(`
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD column stylesheet text not null default '',
|
||||||
|
ADD column google_id text not null default '',
|
||||||
|
ADD column openid_connect_id text not null default ''
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec(`
|
||||||
|
DECLARE my_cursor CURSOR FOR
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
COALESCE(extra->'custom_css', '') as custom_css,
|
||||||
|
COALESCE(extra->'google_id', '') as google_id,
|
||||||
|
COALESCE(extra->'oidc_id', '') as oidc_id
|
||||||
|
FROM users
|
||||||
|
FOR UPDATE
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Exec("CLOSE my_cursor")
|
||||||
|
|
||||||
|
for {
|
||||||
|
var (
|
||||||
|
userID int64
|
||||||
|
customStylesheet string
|
||||||
|
googleID string
|
||||||
|
oidcID string
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := tx.QueryRow(`FETCH NEXT FROM my_cursor`).Scan(&userID, &customStylesheet, &googleID, &oidcID); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := tx.Exec(
|
||||||
|
`UPDATE
|
||||||
|
users
|
||||||
|
SET
|
||||||
|
stylesheet=$2,
|
||||||
|
google_id=$3,
|
||||||
|
openid_connect_id=$4
|
||||||
|
WHERE
|
||||||
|
id=$1
|
||||||
|
`,
|
||||||
|
userID, customStylesheet, googleID, oidcID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
func(tx *sql.Tx) (err error) {
|
||||||
|
_, err = tx.Exec(`
|
||||||
|
ALTER TABLE users DROP COLUMN extra;
|
||||||
|
CREATE UNIQUE INDEX users_google_id_idx ON users(google_id) WHERE google_id <> '';
|
||||||
|
CREATE UNIQUE INDEX users_openid_connect_id_idx ON users(openid_connect_id) WHERE openid_connect_id <> '';
|
||||||
|
`)
|
||||||
|
return err
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,7 +213,7 @@ List of networks allowed to access the metrics endpoint (comma-separated values)
|
||||||
Default is 127.0.0.1/8\&.
|
Default is 127.0.0.1/8\&.
|
||||||
.TP
|
.TP
|
||||||
.B OAUTH2_PROVIDER
|
.B OAUTH2_PROVIDER
|
||||||
OAuth2 provider to use\&. Only google is supported\&.
|
Possible values are "google" or "oidc"\&.
|
||||||
.TP
|
.TP
|
||||||
.B OAUTH2_CLIENT_ID
|
.B OAUTH2_CLIENT_ID
|
||||||
OAuth2 client ID\&.
|
OAuth2 client ID\&.
|
||||||
|
|
|
@ -13,25 +13,27 @@ import (
|
||||||
|
|
||||||
// User represents a user in the system.
|
// User represents a user in the system.
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
IsAdmin bool `json:"is_admin"`
|
IsAdmin bool `json:"is_admin"`
|
||||||
Theme string `json:"theme"`
|
Theme string `json:"theme"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
Timezone string `json:"timezone"`
|
Timezone string `json:"timezone"`
|
||||||
EntryDirection string `json:"entry_sorting_direction"`
|
EntryDirection string `json:"entry_sorting_direction"`
|
||||||
EntriesPerPage int `json:"entries_per_page"`
|
Stylesheet string `json:"stylesheet"`
|
||||||
KeyboardShortcuts bool `json:"keyboard_shortcuts"`
|
GoogleID string `json:"google_id"`
|
||||||
ShowReadingTime bool `json:"show_reading_time"`
|
OpenIDConnectID string `json:"openid_connect_id"`
|
||||||
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
|
EntriesPerPage int `json:"entries_per_page"`
|
||||||
Extra map[string]string `json:"extra"`
|
KeyboardShortcuts bool `json:"keyboard_shortcuts"`
|
||||||
EntrySwipe bool `json:"entry_swipe"`
|
ShowReadingTime bool `json:"show_reading_time"`
|
||||||
|
EntrySwipe bool `json:"entry_swipe"`
|
||||||
|
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUser returns a new User.
|
// NewUser returns a new User.
|
||||||
func NewUser() *User {
|
func NewUser() *User {
|
||||||
return &User{Extra: make(map[string]string)}
|
return &User{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateUserCreation validates new user.
|
// ValidateUserCreation validates new user.
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"miniflux.app/model"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,15 +25,15 @@ type googleProvider struct {
|
||||||
redirectURL string
|
redirectURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g googleProvider) GetUserExtraKey() string {
|
func (g *googleProvider) GetUserExtraKey() string {
|
||||||
return "google_id"
|
return "google_id"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g googleProvider) GetRedirectURL(state string) string {
|
func (g *googleProvider) GetRedirectURL(state string) string {
|
||||||
return g.config().AuthCodeURL(state)
|
return g.config().AuthCodeURL(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g googleProvider) GetProfile(ctx context.Context, code string) (*Profile, error) {
|
func (g *googleProvider) GetProfile(ctx context.Context, code string) (*Profile, error) {
|
||||||
conf := g.config()
|
conf := g.config()
|
||||||
token, err := conf.Exchange(ctx, code)
|
token, err := conf.Exchange(ctx, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -48,14 +50,22 @@ func (g googleProvider) GetProfile(ctx context.Context, code string) (*Profile,
|
||||||
var user googleProfile
|
var user googleProfile
|
||||||
decoder := json.NewDecoder(resp.Body)
|
decoder := json.NewDecoder(resp.Body)
|
||||||
if err := decoder.Decode(&user); err != nil {
|
if err := decoder.Decode(&user); err != nil {
|
||||||
return nil, fmt.Errorf("unable to unserialize google profile: %v", err)
|
return nil, fmt.Errorf("oauth2: unable to unserialize google profile: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
profile := &Profile{Key: g.GetUserExtraKey(), ID: user.Sub, Username: user.Email}
|
profile := &Profile{Key: g.GetUserExtraKey(), ID: user.Sub, Username: user.Email}
|
||||||
return profile, nil
|
return profile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g googleProvider) config() *oauth2.Config {
|
func (g *googleProvider) PopulateUserWithProfileID(user *model.User, profile *Profile) {
|
||||||
|
user.GoogleID = profile.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *googleProvider) UnsetUserProfileID(user *model.User) {
|
||||||
|
user.GoogleID = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *googleProvider) config() *oauth2.Config {
|
||||||
return &oauth2.Config{
|
return &oauth2.Config{
|
||||||
RedirectURL: g.redirectURL,
|
RedirectURL: g.redirectURL,
|
||||||
ClientID: g.clientID,
|
ClientID: g.clientID,
|
||||||
|
|
|
@ -7,6 +7,7 @@ package oauth2 // import "miniflux.app/oauth2"
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"miniflux.app/logger"
|
"miniflux.app/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,8 +16,8 @@ type Manager struct {
|
||||||
providers map[string]Provider
|
providers map[string]Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider returns the given provider.
|
// FindProvider returns the given provider.
|
||||||
func (m *Manager) Provider(name string) (Provider, error) {
|
func (m *Manager) FindProvider(name string) (Provider, error) {
|
||||||
if provider, found := m.providers[name]; found {
|
if provider, found := m.providers[name]; found {
|
||||||
return provider, nil
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,9 @@ package oauth2 // import "miniflux.app/oauth2"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"miniflux.app/model"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc"
|
"github.com/coreos/go-oidc"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
@ -17,15 +20,15 @@ type oidcProvider struct {
|
||||||
provider *oidc.Provider
|
provider *oidc.Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o oidcProvider) GetUserExtraKey() string {
|
func (o *oidcProvider) GetUserExtraKey() string {
|
||||||
return "oidc_id" // FIXME? add extra options key to allow multiple OIDC providers each with their own extra key?
|
return "openid_connect_id"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o oidcProvider) GetRedirectURL(state string) string {
|
func (o *oidcProvider) GetRedirectURL(state string) string {
|
||||||
return o.config().AuthCodeURL(state)
|
return o.config().AuthCodeURL(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o oidcProvider) GetProfile(ctx context.Context, code string) (*Profile, error) {
|
func (o *oidcProvider) GetProfile(ctx context.Context, code string) (*Profile, error) {
|
||||||
conf := o.config()
|
conf := o.config()
|
||||||
token, err := conf.Exchange(ctx, code)
|
token, err := conf.Exchange(ctx, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -41,7 +44,15 @@ func (o oidcProvider) GetProfile(ctx context.Context, code string) (*Profile, er
|
||||||
return profile, nil
|
return profile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o oidcProvider) config() *oauth2.Config {
|
func (o *oidcProvider) PopulateUserWithProfileID(user *model.User, profile *Profile) {
|
||||||
|
user.OpenIDConnectID = profile.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *oidcProvider) UnsetUserProfileID(user *model.User) {
|
||||||
|
user.OpenIDConnectID = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *oidcProvider) config() *oauth2.Config {
|
||||||
return &oauth2.Config{
|
return &oauth2.Config{
|
||||||
RedirectURL: o.redirectURL,
|
RedirectURL: o.redirectURL,
|
||||||
ClientID: o.clientID,
|
ClientID: o.clientID,
|
||||||
|
|
|
@ -3,11 +3,18 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package oauth2 // import "miniflux.app/oauth2"
|
package oauth2 // import "miniflux.app/oauth2"
|
||||||
import "context"
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"miniflux.app/model"
|
||||||
|
)
|
||||||
|
|
||||||
// Provider is an interface for OAuth2 providers.
|
// Provider is an interface for OAuth2 providers.
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
GetUserExtraKey() string
|
GetUserExtraKey() string
|
||||||
GetRedirectURL(state string) string
|
GetRedirectURL(state string) string
|
||||||
GetProfile(ctx context.Context, code string) (*Profile, error)
|
GetProfile(ctx context.Context, code string) (*Profile, error)
|
||||||
|
PopulateUserWithProfileID(user *model.User, profile *Profile)
|
||||||
|
UnsetUserProfileID(user *model.User)
|
||||||
}
|
}
|
||||||
|
|
140
storage/user.go
140
storage/user.go
|
@ -12,7 +12,7 @@ import (
|
||||||
"miniflux.app/logger"
|
"miniflux.app/logger"
|
||||||
"miniflux.app/model"
|
"miniflux.app/model"
|
||||||
|
|
||||||
"github.com/lib/pq/hstore"
|
"github.com/lib/pq"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,32 +54,37 @@ func (s *Storage) AnotherUserExists(userID int64, username string) bool {
|
||||||
|
|
||||||
// CreateUser creates a new user.
|
// CreateUser creates a new user.
|
||||||
func (s *Storage) CreateUser(user *model.User) (err error) {
|
func (s *Storage) CreateUser(user *model.User) (err error) {
|
||||||
password := ""
|
hashedPassword := ""
|
||||||
extra := hstore.Hstore{Map: make(map[string]sql.NullString)}
|
|
||||||
|
|
||||||
if user.Password != "" {
|
if user.Password != "" {
|
||||||
password, err = hashPassword(user.Password)
|
hashedPassword, err = hashPassword(user.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(user.Extra) > 0 {
|
|
||||||
for key, value := range user.Extra {
|
|
||||||
extra.Map[key] = sql.NullString{String: value, Valid: true}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query := `
|
query := `
|
||||||
INSERT INTO users
|
INSERT INTO users
|
||||||
(username, password, is_admin, extra)
|
(username, password, is_admin, google_id, openid_connect_id)
|
||||||
VALUES
|
VALUES
|
||||||
(LOWER($1), $2, $3, $4)
|
(LOWER($1), $2, $3, $4, $5)
|
||||||
RETURNING
|
RETURNING
|
||||||
id, username, is_admin, language, theme, timezone, entry_direction, entries_per_page, keyboard_shortcuts, show_reading_time, entry_swipe
|
id,
|
||||||
|
username,
|
||||||
|
is_admin,
|
||||||
|
language,
|
||||||
|
theme,
|
||||||
|
timezone,
|
||||||
|
entry_direction,
|
||||||
|
entries_per_page,
|
||||||
|
keyboard_shortcuts,
|
||||||
|
show_reading_time,
|
||||||
|
entry_swipe,
|
||||||
|
stylesheet,
|
||||||
|
google_id,
|
||||||
|
openid_connect_id
|
||||||
`
|
`
|
||||||
|
|
||||||
err = s.db.QueryRow(query, user.Username, password, user.IsAdmin, extra).Scan(
|
err = s.db.QueryRow(query, user.Username, hashedPassword, user.IsAdmin, user.GoogleID, user.OpenIDConnectID).Scan(
|
||||||
&user.ID,
|
&user.ID,
|
||||||
&user.Username,
|
&user.Username,
|
||||||
&user.IsAdmin,
|
&user.IsAdmin,
|
||||||
|
@ -91,6 +96,9 @@ func (s *Storage) CreateUser(user *model.User) (err error) {
|
||||||
&user.KeyboardShortcuts,
|
&user.KeyboardShortcuts,
|
||||||
&user.ShowReadingTime,
|
&user.ShowReadingTime,
|
||||||
&user.EntrySwipe,
|
&user.EntrySwipe,
|
||||||
|
&user.Stylesheet,
|
||||||
|
&user.GoogleID,
|
||||||
|
&user.OpenIDConnectID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(`store: unable to create user: %v`, err)
|
return fmt.Errorf(`store: unable to create user: %v`, err)
|
||||||
|
@ -101,26 +109,6 @@ func (s *Storage) CreateUser(user *model.User) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateExtraField updates an extra field of the given user.
|
|
||||||
func (s *Storage) UpdateExtraField(userID int64, field, value string) error {
|
|
||||||
query := fmt.Sprintf(`UPDATE users SET extra = extra || hstore('%s', $1) WHERE id=$2`, field)
|
|
||||||
_, err := s.db.Exec(query, value, userID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(`store: unable to update user extra field: %v`, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveExtraField deletes an extra field for the given user.
|
|
||||||
func (s *Storage) RemoveExtraField(userID int64, field string) error {
|
|
||||||
query := `UPDATE users SET extra = delete(extra, $1) WHERE id=$2`
|
|
||||||
_, err := s.db.Exec(query, field, userID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(`store: unable to remove user extra field: %v`, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateUser updates a user.
|
// UpdateUser updates a user.
|
||||||
func (s *Storage) UpdateUser(user *model.User) error {
|
func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
if user.Password != "" {
|
if user.Password != "" {
|
||||||
|
@ -141,9 +129,12 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
entries_per_page=$8,
|
entries_per_page=$8,
|
||||||
keyboard_shortcuts=$9,
|
keyboard_shortcuts=$9,
|
||||||
show_reading_time=$10,
|
show_reading_time=$10,
|
||||||
entry_swipe=$11
|
entry_swipe=$11,
|
||||||
|
stylesheet=$12,
|
||||||
|
google_id=$13,
|
||||||
|
openid_connect_id=$14
|
||||||
WHERE
|
WHERE
|
||||||
id=$12
|
id=$15
|
||||||
`
|
`
|
||||||
|
|
||||||
_, err = s.db.Exec(
|
_, err = s.db.Exec(
|
||||||
|
@ -159,6 +150,9 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
user.KeyboardShortcuts,
|
user.KeyboardShortcuts,
|
||||||
user.ShowReadingTime,
|
user.ShowReadingTime,
|
||||||
user.EntrySwipe,
|
user.EntrySwipe,
|
||||||
|
user.Stylesheet,
|
||||||
|
user.GoogleID,
|
||||||
|
user.OpenIDConnectID,
|
||||||
user.ID,
|
user.ID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -176,9 +170,12 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
entries_per_page=$7,
|
entries_per_page=$7,
|
||||||
keyboard_shortcuts=$8,
|
keyboard_shortcuts=$8,
|
||||||
show_reading_time=$9,
|
show_reading_time=$9,
|
||||||
entry_swipe=$10
|
entry_swipe=$10,
|
||||||
|
stylesheet=$11,
|
||||||
|
google_id=$12,
|
||||||
|
openid_connect_id=$13
|
||||||
WHERE
|
WHERE
|
||||||
id=$11
|
id=$14
|
||||||
`
|
`
|
||||||
|
|
||||||
_, err := s.db.Exec(
|
_, err := s.db.Exec(
|
||||||
|
@ -193,6 +190,9 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
user.KeyboardShortcuts,
|
user.KeyboardShortcuts,
|
||||||
user.ShowReadingTime,
|
user.ShowReadingTime,
|
||||||
user.EntrySwipe,
|
user.EntrySwipe,
|
||||||
|
user.Stylesheet,
|
||||||
|
user.GoogleID,
|
||||||
|
user.OpenIDConnectID,
|
||||||
user.ID,
|
user.ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -201,10 +201,6 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.UpdateExtraField(user.ID, "custom_css", user.Extra["custom_css"]); err != nil {
|
|
||||||
return fmt.Errorf(`store: unable to update user custom css: %v`, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,7 +230,9 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) {
|
||||||
show_reading_time,
|
show_reading_time,
|
||||||
entry_swipe,
|
entry_swipe,
|
||||||
last_login_at,
|
last_login_at,
|
||||||
extra
|
stylesheet,
|
||||||
|
google_id,
|
||||||
|
openid_connect_id
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -259,7 +257,9 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) {
|
||||||
show_reading_time,
|
show_reading_time,
|
||||||
entry_swipe,
|
entry_swipe,
|
||||||
last_login_at,
|
last_login_at,
|
||||||
extra
|
stylesheet,
|
||||||
|
google_id,
|
||||||
|
openid_connect_id
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -268,8 +268,8 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) {
|
||||||
return s.fetchUser(query, username)
|
return s.fetchUser(query, username)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserByExtraField finds a user by an extra field value.
|
// UserByField finds a user by a field value.
|
||||||
func (s *Storage) UserByExtraField(field, value string) (*model.User, error) {
|
func (s *Storage) UserByField(field, value string) (*model.User, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
|
@ -284,13 +284,22 @@ func (s *Storage) UserByExtraField(field, value string) (*model.User, error) {
|
||||||
show_reading_time,
|
show_reading_time,
|
||||||
entry_swipe,
|
entry_swipe,
|
||||||
last_login_at,
|
last_login_at,
|
||||||
extra
|
stylesheet,
|
||||||
|
google_id,
|
||||||
|
openid_connect_id
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
WHERE
|
WHERE
|
||||||
extra->$1=$2
|
%s=$1
|
||||||
`
|
`
|
||||||
return s.fetchUser(query, field, value)
|
return s.fetchUser(fmt.Sprintf(query, pq.QuoteIdentifier(field)), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnotherUserWithFieldExists returns true if a user has the value set for the given field.
|
||||||
|
func (s *Storage) AnotherUserWithFieldExists(userID int64, field, value string) bool {
|
||||||
|
var result bool
|
||||||
|
s.db.QueryRow(fmt.Sprintf(`SELECT true FROM users WHERE id <> $1 AND %s=$2`, pq.QuoteIdentifier(field)), userID, value).Scan(&result)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserByAPIKey returns a User from an API Key.
|
// UserByAPIKey returns a User from an API Key.
|
||||||
|
@ -309,7 +318,9 @@ func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
|
||||||
u.show_reading_time,
|
u.show_reading_time,
|
||||||
u.entry_swipe,
|
u.entry_swipe,
|
||||||
u.last_login_at,
|
u.last_login_at,
|
||||||
u.extra
|
u.stylesheet,
|
||||||
|
u.google_id,
|
||||||
|
u.openid_connect_id
|
||||||
FROM
|
FROM
|
||||||
users u
|
users u
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
|
@ -321,8 +332,6 @@ func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, error) {
|
func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, error) {
|
||||||
var extra hstore.Hstore
|
|
||||||
|
|
||||||
user := model.NewUser()
|
user := model.NewUser()
|
||||||
err := s.db.QueryRow(query, args...).Scan(
|
err := s.db.QueryRow(query, args...).Scan(
|
||||||
&user.ID,
|
&user.ID,
|
||||||
|
@ -337,7 +346,9 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err
|
||||||
&user.ShowReadingTime,
|
&user.ShowReadingTime,
|
||||||
&user.EntrySwipe,
|
&user.EntrySwipe,
|
||||||
&user.LastLoginAt,
|
&user.LastLoginAt,
|
||||||
&extra,
|
&user.Stylesheet,
|
||||||
|
&user.GoogleID,
|
||||||
|
&user.OpenIDConnectID,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
|
@ -346,12 +357,6 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err
|
||||||
return nil, fmt.Errorf(`store: unable to fetch user: %v`, err)
|
return nil, fmt.Errorf(`store: unable to fetch user: %v`, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range extra.Map {
|
|
||||||
if value.Valid {
|
|
||||||
user.Extra[key] = value.String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,7 +409,9 @@ func (s *Storage) Users() (model.Users, error) {
|
||||||
show_reading_time,
|
show_reading_time,
|
||||||
entry_swipe,
|
entry_swipe,
|
||||||
last_login_at,
|
last_login_at,
|
||||||
extra
|
stylesheet,
|
||||||
|
google_id,
|
||||||
|
openid_connect_id
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
ORDER BY username ASC
|
ORDER BY username ASC
|
||||||
|
@ -417,7 +424,6 @@ func (s *Storage) Users() (model.Users, error) {
|
||||||
|
|
||||||
var users model.Users
|
var users model.Users
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var extra hstore.Hstore
|
|
||||||
user := model.NewUser()
|
user := model.NewUser()
|
||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
&user.ID,
|
&user.ID,
|
||||||
|
@ -432,19 +438,15 @@ func (s *Storage) Users() (model.Users, error) {
|
||||||
&user.ShowReadingTime,
|
&user.ShowReadingTime,
|
||||||
&user.EntrySwipe,
|
&user.EntrySwipe,
|
||||||
&user.LastLoginAt,
|
&user.LastLoginAt,
|
||||||
&extra,
|
&user.Stylesheet,
|
||||||
|
&user.GoogleID,
|
||||||
|
&user.OpenIDConnectID,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(`store: unable to fetch users row: %v`, err)
|
return nil, fmt.Errorf(`store: unable to fetch users row: %v`, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range extra.Map {
|
|
||||||
if value.Valid {
|
|
||||||
user.Extra[key] = value.String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
users = append(users, user)
|
users = append(users, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -346,9 +346,9 @@ SOFTWARE.
|
||||||
|
|
||||||
<meta name="theme-color" content="{{ theme_color .theme }}">
|
<meta name="theme-color" content="{{ theme_color .theme }}">
|
||||||
<link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" .theme }}?{{ .theme_checksum }}">
|
<link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" .theme }}?{{ .theme_checksum }}">
|
||||||
{{ if .user }} {{ if ne (index .user.Extra "custom_css") ("") }}
|
{{ if and .user .user.Stylesheet }}
|
||||||
<link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" "custom_css" }}">
|
<link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" "custom_css" }}">
|
||||||
{{ end }}{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ route "javascript" "name" "app" }}?{{ .app_js_checksum }}" defer></script>
|
<script type="text/javascript" src="{{ route "javascript" "name" "app" }}?{{ .app_js_checksum }}" defer></script>
|
||||||
<script type="text/javascript" src="{{ route "javascript" "name" "service-worker" }}?{{ .sw_js_checksum }}" defer id="service-worker-script"></script>
|
<script type="text/javascript" src="{{ route "javascript" "name" "service-worker" }}?{{ .sw_js_checksum }}" defer id="service-worker-script"></script>
|
||||||
|
@ -524,7 +524,7 @@ var templateCommonMapChecksums = map[string]string{
|
||||||
"feed_menu": "318d8662dda5ca9dfc75b909c8461e79c86fb5082df1428f67aaf856f19f4b50",
|
"feed_menu": "318d8662dda5ca9dfc75b909c8461e79c86fb5082df1428f67aaf856f19f4b50",
|
||||||
"icons": "9a41753778072f286216085d8712495e2ccca20c7a24f5c982775436a3d38579",
|
"icons": "9a41753778072f286216085d8712495e2ccca20c7a24f5c982775436a3d38579",
|
||||||
"item_meta": "56ab09d7dd46eeb2e2ee11ddcec0c157a5832c896dbd2887d9e2b013680b2af6",
|
"item_meta": "56ab09d7dd46eeb2e2ee11ddcec0c157a5832c896dbd2887d9e2b013680b2af6",
|
||||||
"layout": "65767e7dbebe1f7ed42895ecd5a737b0693e4a2ec35e84e3e391f462beb11977",
|
"layout": "c4b8c65c0d85ed1aff0550f58b9dbf0768c74f2df6232952c0fe299d4c73d674",
|
||||||
"pagination": "7b61288e86283c4cf0dc83bcbf8bf1c00c7cb29e60201c8c0b633b2450d2911f",
|
"pagination": "7b61288e86283c4cf0dc83bcbf8bf1c00c7cb29e60201c8c0b633b2450d2911f",
|
||||||
"settings_menu": "e2b777630c0efdbc529800303c01d6744ed3af80ec505ac5a5b3f99c9b989156",
|
"settings_menu": "e2b777630c0efdbc529800303c01d6744ed3af80ec505ac5a5b3f99c9b989156",
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,9 +35,9 @@
|
||||||
|
|
||||||
<meta name="theme-color" content="{{ theme_color .theme }}">
|
<meta name="theme-color" content="{{ theme_color .theme }}">
|
||||||
<link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" .theme }}?{{ .theme_checksum }}">
|
<link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" .theme }}?{{ .theme_checksum }}">
|
||||||
{{ if .user }} {{ if ne (index .user.Extra "custom_css") ("") }}
|
{{ if and .user .user.Stylesheet }}
|
||||||
<link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" "custom_css" }}">
|
<link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" "custom_css" }}">
|
||||||
{{ end }}{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ route "javascript" "name" "app" }}?{{ .app_js_checksum }}" defer></script>
|
<script type="text/javascript" src="{{ route "javascript" "name" "app" }}?{{ .app_js_checksum }}" defer></script>
|
||||||
<script type="text/javascript" src="{{ route "javascript" "name" "service-worker" }}?{{ .sw_js_checksum }}" defer id="service-worker-script"></script>
|
<script type="text/javascript" src="{{ route "javascript" "name" "service-worker" }}?{{ .sw_js_checksum }}" defer id="service-worker-script"></script>
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
|
|
||||||
{{ if hasOAuth2Provider "google" }}
|
{{ if hasOAuth2Provider "google" }}
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
{{ if hasKey .user.Extra "google_id" }}
|
{{ if .user.GoogleID }}
|
||||||
<a href="{{ route "oauth2Unlink" "provider" "google" }}">{{ t "page.settings.unlink_google_account" }}</a>
|
<a href="{{ route "oauth2Unlink" "provider" "google" }}">{{ t "page.settings.unlink_google_account" }}</a>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a href="{{ route "oauth2Redirect" "provider" "google" }}">{{ t "page.settings.link_google_account" }}</a>
|
<a href="{{ route "oauth2Redirect" "provider" "google" }}">{{ t "page.settings.link_google_account" }}</a>
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{ else if hasOAuth2Provider "oidc" }}
|
{{ else if hasOAuth2Provider "oidc" }}
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
{{ if hasKey .user.Extra "oidc_id" }}
|
{{ if .user.OpenIDConnectID }}
|
||||||
<a href="{{ route "oauth2Unlink" "provider" "oidc" }}">{{ t "page.settings.unlink_oidc_account" }}</a>
|
<a href="{{ route "oauth2Unlink" "provider" "oidc" }}">{{ t "page.settings.unlink_oidc_account" }}</a>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a href="{{ route "oauth2Redirect" "provider" "oidc" }}">{{ t "page.settings.link_oidc_account" }}</a>
|
<a href="{{ route "oauth2Redirect" "provider" "oidc" }}">{{ t "page.settings.link_oidc_account" }}</a>
|
||||||
|
|
|
@ -1387,7 +1387,7 @@ var templateViewsMap = map[string]string{
|
||||||
|
|
||||||
{{ if hasOAuth2Provider "google" }}
|
{{ if hasOAuth2Provider "google" }}
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
{{ if hasKey .user.Extra "google_id" }}
|
{{ if .user.GoogleID }}
|
||||||
<a href="{{ route "oauth2Unlink" "provider" "google" }}">{{ t "page.settings.unlink_google_account" }}</a>
|
<a href="{{ route "oauth2Unlink" "provider" "google" }}">{{ t "page.settings.unlink_google_account" }}</a>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a href="{{ route "oauth2Redirect" "provider" "google" }}">{{ t "page.settings.link_google_account" }}</a>
|
<a href="{{ route "oauth2Redirect" "provider" "google" }}">{{ t "page.settings.link_google_account" }}</a>
|
||||||
|
@ -1395,7 +1395,7 @@ var templateViewsMap = map[string]string{
|
||||||
</div>
|
</div>
|
||||||
{{ else if hasOAuth2Provider "oidc" }}
|
{{ else if hasOAuth2Provider "oidc" }}
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
{{ if hasKey .user.Extra "oidc_id" }}
|
{{ if .user.OpenIDConnectID }}
|
||||||
<a href="{{ route "oauth2Unlink" "provider" "oidc" }}">{{ t "page.settings.unlink_oidc_account" }}</a>
|
<a href="{{ route "oauth2Unlink" "provider" "oidc" }}">{{ t "page.settings.unlink_oidc_account" }}</a>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a href="{{ route "oauth2Redirect" "provider" "oidc" }}">{{ t "page.settings.link_oidc_account" }}</a>
|
<a href="{{ route "oauth2Redirect" "provider" "oidc" }}">{{ t "page.settings.link_oidc_account" }}</a>
|
||||||
|
@ -1624,7 +1624,7 @@ var templateViewsMapChecksums = map[string]string{
|
||||||
"login": "9165434b2405e9332de4bebbb54a93dc5692276ea72e7c5e07f655a002dfd290",
|
"login": "9165434b2405e9332de4bebbb54a93dc5692276ea72e7c5e07f655a002dfd290",
|
||||||
"search_entries": "6a3e5876cb7541a2f08f56e30ab46a2d7d64894ec5e170f627b2dd674d8aeefe",
|
"search_entries": "6a3e5876cb7541a2f08f56e30ab46a2d7d64894ec5e170f627b2dd674d8aeefe",
|
||||||
"sessions": "5d5c677bddbd027e0b0c9f7a0dd95b66d9d95b4e130959f31fb955b926c2201c",
|
"sessions": "5d5c677bddbd027e0b0c9f7a0dd95b66d9d95b4e130959f31fb955b926c2201c",
|
||||||
"settings": "6f77f9431beb9aa2e28840a60a6463d1a9eb0e92c1929b204584c85c71d0c7a3",
|
"settings": "ef2155983f362ef001e0d9b27536a3bea9e04869ee600e8285fff894c39ba1c1",
|
||||||
"shared_entries": "f87a42bf44dc3606c5a44b185263c1b9a612a8ae194f75061253d4dde7b095a2",
|
"shared_entries": "f87a42bf44dc3606c5a44b185263c1b9a612a8ae194f75061253d4dde7b095a2",
|
||||||
"unread_entries": "21c584da7ca8192655c62f16a7ac92dbbfdf1307588ffe51eb4a8bbf3f9f7526",
|
"unread_entries": "21c584da7ca8192655c62f16a7ac92dbbfdf1307588ffe51eb4a8bbf3f9f7526",
|
||||||
"users": "d7ff52efc582bbad10504f4a04fa3adcc12d15890e45dff51cac281e0c446e45",
|
"users": "d7ff52efc582bbad10504f4a04fa3adcc12d15890e45dff51cac281e0c446e45",
|
||||||
|
|
|
@ -38,8 +38,8 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
|
||||||
user.EntriesPerPage = s.EntriesPerPage
|
user.EntriesPerPage = s.EntriesPerPage
|
||||||
user.KeyboardShortcuts = s.KeyboardShortcuts
|
user.KeyboardShortcuts = s.KeyboardShortcuts
|
||||||
user.ShowReadingTime = s.ShowReadingTime
|
user.ShowReadingTime = s.ShowReadingTime
|
||||||
user.Extra["custom_css"] = s.CustomCSS
|
user.Stylesheet = s.CustomCSS
|
||||||
user.EntrySwipe = s.EntrySwipe;
|
user.EntrySwipe = s.EntrySwipe
|
||||||
|
|
||||||
if s.Password != "" {
|
if s.Password != "" {
|
||||||
user.Password = s.Password
|
user.Password = s.Password
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package ui // import "miniflux.app/ui"
|
package ui // import "miniflux.app/ui"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"miniflux.app/config"
|
"miniflux.app/config"
|
||||||
|
@ -44,7 +45,7 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authProvider, err := getOAuth2Manager(r.Context()).Provider(provider)
|
authProvider, err := getOAuth2Manager(r.Context()).FindProvider(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[OAuth2] %v", err)
|
logger.Error("[OAuth2] %v", err)
|
||||||
html.Redirect(w, r, route.Path(h.router, "login"))
|
html.Redirect(w, r, route.Path(h.router, "login"))
|
||||||
|
@ -61,20 +62,21 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||||
logger.Info("[OAuth2] [ClientIP=%s] Successful auth for %s", clientIP, profile)
|
logger.Info("[OAuth2] [ClientIP=%s] Successful auth for %s", clientIP, profile)
|
||||||
|
|
||||||
if request.IsAuthenticated(r) {
|
if request.IsAuthenticated(r) {
|
||||||
user, err := h.store.UserByExtraField(profile.Key, profile.ID)
|
loggedUser, err := h.store.UserByID(request.UserID(r))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
html.ServerError(w, r, err)
|
html.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != nil {
|
if h.store.AnotherUserWithFieldExists(loggedUser.ID, profile.Key, profile.ID) {
|
||||||
logger.Error("[OAuth2] User #%d cannot be associated because %s is already associated", request.UserID(r), user.Username)
|
logger.Error("[OAuth2] User #%d cannot be associated because it is already associated with another user", loggedUser.ID)
|
||||||
sess.NewFlashErrorMessage(printer.Printf("error.duplicate_linked_account"))
|
sess.NewFlashErrorMessage(printer.Printf("error.duplicate_linked_account"))
|
||||||
html.Redirect(w, r, route.Path(h.router, "settings"))
|
html.Redirect(w, r, route.Path(h.router, "settings"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.store.UpdateExtraField(request.UserID(r), profile.Key, profile.ID); err != nil {
|
authProvider.PopulateUserWithProfileID(loggedUser, profile)
|
||||||
|
if err := h.store.UpdateUser(loggedUser); err != nil {
|
||||||
html.ServerError(w, r, err)
|
html.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -84,7 +86,7 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := h.store.UserByExtraField(profile.Key, profile.ID)
|
user, err := h.store.UserByField(profile.Key, profile.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
html.ServerError(w, r, err)
|
html.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
|
@ -96,10 +98,15 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if h.store.UserExists(profile.Username) {
|
||||||
|
html.BadRequest(w, r, errors.New(printer.Printf("error.user_already_exists")))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
user = model.NewUser()
|
user = model.NewUser()
|
||||||
user.Username = profile.Username
|
user.Username = profile.Username
|
||||||
user.IsAdmin = false
|
user.IsAdmin = false
|
||||||
user.Extra[profile.Key] = profile.ID
|
authProvider.PopulateUserWithProfileID(user, profile)
|
||||||
|
|
||||||
if err := h.store.CreateUser(user); err != nil {
|
if err := h.store.CreateUser(user); err != nil {
|
||||||
html.ServerError(w, r, err)
|
html.ServerError(w, r, err)
|
||||||
|
|
|
@ -24,7 +24,7 @@ func (h *handler) oauth2Redirect(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authProvider, err := getOAuth2Manager(r.Context()).Provider(provider)
|
authProvider, err := getOAuth2Manager(r.Context()).FindProvider(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[OAuth2] %v", err)
|
logger.Error("[OAuth2] %v", err)
|
||||||
html.Redirect(w, r, route.Path(h.router, "login"))
|
html.Redirect(w, r, route.Path(h.router, "login"))
|
||||||
|
|
|
@ -24,7 +24,7 @@ func (h *handler) oauth2Unlink(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authProvider, err := getOAuth2Manager(r.Context()).Provider(provider)
|
authProvider, err := getOAuth2Manager(r.Context()).FindProvider(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[OAuth2] %v", err)
|
logger.Error("[OAuth2] %v", err)
|
||||||
html.Redirect(w, r, route.Path(h.router, "settings"))
|
html.Redirect(w, r, route.Path(h.router, "settings"))
|
||||||
|
@ -32,6 +32,11 @@ func (h *handler) oauth2Unlink(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
sess := session.New(h.store, request.SessionID(r))
|
sess := session.New(h.store, request.SessionID(r))
|
||||||
|
user, err := h.store.UserByID(request.UserID(r))
|
||||||
|
if err != nil {
|
||||||
|
html.ServerError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
hasPassword, err := h.store.HasPassword(request.UserID(r))
|
hasPassword, err := h.store.HasPassword(request.UserID(r))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -45,7 +50,8 @@ func (h *handler) oauth2Unlink(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.store.RemoveExtraField(request.UserID(r), authProvider.GetUserExtraKey()); err != nil {
|
authProvider.UnsetUserProfileID(user)
|
||||||
|
if err := h.store.UpdateUser(user); err != nil {
|
||||||
html.ServerError(w, r, err)
|
html.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
|
||||||
EntriesPerPage: user.EntriesPerPage,
|
EntriesPerPage: user.EntriesPerPage,
|
||||||
KeyboardShortcuts: user.KeyboardShortcuts,
|
KeyboardShortcuts: user.KeyboardShortcuts,
|
||||||
ShowReadingTime: user.ShowReadingTime,
|
ShowReadingTime: user.ShowReadingTime,
|
||||||
CustomCSS: user.Extra["custom_css"],
|
CustomCSS: user.Stylesheet,
|
||||||
EntrySwipe: user.EntrySwipe,
|
EntrySwipe: user.EntrySwipe,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,22 +18,17 @@ func (h *handler) showStylesheet(w http.ResponseWriter, r *http.Request) {
|
||||||
filename := request.RouteStringParam(r, "name")
|
filename := request.RouteStringParam(r, "name")
|
||||||
if filename == "custom_css" {
|
if filename == "custom_css" {
|
||||||
user, err := h.store.UserByID(request.UserID(r))
|
user, err := h.store.UserByID(request.UserID(r))
|
||||||
if err != nil {
|
if err != nil || user == nil {
|
||||||
html.NotFound(w, r)
|
html.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b := response.New(w, r)
|
b := response.New(w, r)
|
||||||
if user == nil {
|
|
||||||
b.WithHeader("Content-Type", "text/css; charset=utf-8")
|
|
||||||
b.WithBody("")
|
|
||||||
b.Write()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.WithHeader("Content-Type", "text/css; charset=utf-8")
|
b.WithHeader("Content-Type", "text/css; charset=utf-8")
|
||||||
b.WithBody(user.Extra["custom_css"])
|
b.WithBody(user.Stylesheet)
|
||||||
b.Write()
|
b.Write()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
etag, found := static.StylesheetsChecksums[filename]
|
etag, found := static.StylesheetsChecksums[filename]
|
||||||
if !found {
|
if !found {
|
||||||
html.NotFound(w, r)
|
html.NotFound(w, r)
|
||||||
|
|
Loading…
Reference in a new issue