Skip to content

Commit

Permalink
Remove extra column from users table (HSTORE field)
Browse files Browse the repository at this point in the history
Migrated key/value pairs to specific columns.
  • Loading branch information
fguillot committed Dec 22, 2020
1 parent ae74f94 commit 83f3cca
Show file tree
Hide file tree
Showing 19 changed files with 256 additions and 141 deletions.
27 changes: 16 additions & 11 deletions client/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,22 @@ const (

// User represents a user in the system.
type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
IsAdmin bool `json:"is_admin"`
Theme string `json:"theme"`
Language string `json:"language"`
Timezone string `json:"timezone"`
EntryDirection string `json:"entry_sorting_direction"`
EntriesPerPage int `json:"entries_per_page"`
LastLoginAt *time.Time `json:"last_login_at"`
Extra map[string]string `json:"extra"`
ID int64 `json:"id"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
IsAdmin bool `json:"is_admin"`
Theme string `json:"theme"`
Language string `json:"language"`
Timezone string `json:"timezone"`
EntryDirection string `json:"entry_sorting_direction"`
Stylesheet string `json:"stylesheet"`
GoogleID string `json:"google_id"`
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 {
Expand Down
71 changes: 70 additions & 1 deletion database/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

package database // import "miniflux.app/database"

import "database/sql"
import (
"database/sql"
)

var schemaVersion = len(migrations)

Expand Down Expand Up @@ -427,4 +429,71 @@ var migrations = []func(tx *sql.Tx) error{
_, err = tx.Exec(sql)
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
},
}
2 changes: 1 addition & 1 deletion miniflux.1
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ List of networks allowed to access the metrics endpoint (comma-separated values)
Default is 127.0.0.1/8\&.
.TP
.B OAUTH2_PROVIDER
OAuth2 provider to use\&. Only google is supported\&.
Possible values are "google" or "oidc"\&.
.TP
.B OAUTH2_CLIENT_ID
OAuth2 client ID\&.
Expand Down
32 changes: 17 additions & 15 deletions model/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,27 @@ import (

// User represents a user in the system.
type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
IsAdmin bool `json:"is_admin"`
Theme string `json:"theme"`
Language string `json:"language"`
Timezone string `json:"timezone"`
EntryDirection string `json:"entry_sorting_direction"`
EntriesPerPage int `json:"entries_per_page"`
KeyboardShortcuts bool `json:"keyboard_shortcuts"`
ShowReadingTime bool `json:"show_reading_time"`
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
Extra map[string]string `json:"extra"`
EntrySwipe bool `json:"entry_swipe"`
ID int64 `json:"id"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
IsAdmin bool `json:"is_admin"`
Theme string `json:"theme"`
Language string `json:"language"`
Timezone string `json:"timezone"`
EntryDirection string `json:"entry_sorting_direction"`
Stylesheet string `json:"stylesheet"`
GoogleID string `json:"google_id"`
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,omitempty"`
}

// NewUser returns a new User.
func NewUser() *User {
return &User{Extra: make(map[string]string)}
return &User{}
}

// ValidateUserCreation validates new user.
Expand Down
20 changes: 15 additions & 5 deletions oauth2/google.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"encoding/json"
"fmt"

"miniflux.app/model"

"golang.org/x/oauth2"
)

Expand All @@ -23,15 +25,15 @@ type googleProvider struct {
redirectURL string
}

func (g googleProvider) GetUserExtraKey() string {
func (g *googleProvider) GetUserExtraKey() string {
return "google_id"
}

func (g googleProvider) GetRedirectURL(state string) string {
func (g *googleProvider) GetRedirectURL(state string) string {
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()
token, err := conf.Exchange(ctx, code)
if err != nil {
Expand All @@ -48,14 +50,22 @@ func (g googleProvider) GetProfile(ctx context.Context, code string) (*Profile,
var user googleProfile
decoder := json.NewDecoder(resp.Body)
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}
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{
RedirectURL: g.redirectURL,
ClientID: g.clientID,
Expand Down
5 changes: 3 additions & 2 deletions oauth2/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package oauth2 // import "miniflux.app/oauth2"
import (
"context"
"errors"

"miniflux.app/logger"
)

Expand All @@ -15,8 +16,8 @@ type Manager struct {
providers map[string]Provider
}

// Provider returns the given provider.
func (m *Manager) Provider(name string) (Provider, error) {
// FindProvider returns the given provider.
func (m *Manager) FindProvider(name string) (Provider, error) {
if provider, found := m.providers[name]; found {
return provider, nil
}
Expand Down
21 changes: 16 additions & 5 deletions oauth2/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ package oauth2 // import "miniflux.app/oauth2"

import (
"context"

"miniflux.app/model"

"github.com/coreos/go-oidc"
"golang.org/x/oauth2"
)
Expand All @@ -17,15 +20,15 @@ type oidcProvider struct {
provider *oidc.Provider
}

func (o oidcProvider) GetUserExtraKey() string {
return "oidc_id" // FIXME? add extra options key to allow multiple OIDC providers each with their own extra key?
func (o *oidcProvider) GetUserExtraKey() string {
return "openid_connect_id"
}

func (o oidcProvider) GetRedirectURL(state string) string {
func (o *oidcProvider) GetRedirectURL(state string) string {
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()
token, err := conf.Exchange(ctx, code)
if err != nil {
Expand All @@ -41,7 +44,15 @@ func (o oidcProvider) GetProfile(ctx context.Context, code string) (*Profile, er
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{
RedirectURL: o.redirectURL,
ClientID: o.clientID,
Expand Down
9 changes: 8 additions & 1 deletion oauth2/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
// license that can be found in the LICENSE file.

package oauth2 // import "miniflux.app/oauth2"
import "context"

import (
"context"

"miniflux.app/model"
)

// Provider is an interface for OAuth2 providers.
type Provider interface {
GetUserExtraKey() string
GetRedirectURL(state string) string
GetProfile(ctx context.Context, code string) (*Profile, error)
PopulateUserWithProfileID(user *model.User, profile *Profile)
UnsetUserProfileID(user *model.User)
}
Loading

0 comments on commit 83f3cca

Please sign in to comment.