6 Commits 84dc991642 ... 953ee5baec

Author SHA1 Message Date
  Adam 953ee5baec add icons to hamburger menu 4 years ago
  Adam e5c0ff3156 show avatar 4 years ago
  Adam db0c29cbe1 log out on expired session in index 4 years ago
  Adam bff986631e add hamburger menu 4 years ago
  Adam be4a07ad26 verify session in film 4 years ago
  Adam abdb8c4ea7 verify session token in index 4 years ago
10 changed files with 155 additions and 40 deletions
  1. 14 2
      accounts/common.go
  2. 14 15
      accounts/login.go
  3. 65 9
      db/db.go
  4. 7 3
      front/html.go
  5. 3 2
      front/renderer.go
  6. 29 2
      libamuse/account.go
  7. 13 1
      libamuse/common.go
  8. 6 4
      libamuse/film.go
  9. 4 2
      libamuse/index.go
  10. 0 0
      libamuse/user.go

+ 14 - 2
accounts/common.go

@@ -6,8 +6,20 @@ type User struct {
 }
 
 func (u User) IsEmpty() bool {
-	emptyUser := User{}
-	return u == emptyUser
+	return u.Username == ""
+}
+
+type AuthenticationSource int
+
+const (
+	Header AuthenticationSource = iota
+	Cookie
+)
+
+type Authentication struct {
+	Token string
+	Source AuthenticationSource
+	CsrfToken string
 }
 
 type AuthError struct {

+ 14 - 15
accounts/login.go

@@ -6,13 +6,12 @@ import (
 	"notabug.org/apiote/amuse/db"
 
 	"bytes"
-	"database/sql"
+	"crypto/rand"
 	"encoding/base64"
+	"encoding/hex"
 	"errors"
 	"fmt"
 	"strings"
-	"crypto/rand"
-	"encoding/hex"
 
 	"github.com/pquerna/otp/totp"
 	"golang.org/x/crypto/argon2"
@@ -28,7 +27,7 @@ type AuthData struct {
 }
 
 type AuthResult struct {
-	row              *sql.Rows
+	user             *db.User
 	passwordHash     string
 	sfaSecret        string
 	recoveryCodesRaw string
@@ -51,21 +50,21 @@ type Argon struct {
 func findUser(args ...interface{}) (interface{}, error) {
 	authData := args[0].(*AuthData)
 	authResult := args[1].(*AuthResult)
-	row, err := db.GetUser(authData.username)
-	authResult.row = row
+	user, err := db.GetUser(authData.username)
+	authResult.user = user
 	if empty, ok := err.(db.EmptyError); ok {
 		err = AuthError{Err: empty}
 	}
 	return gott.Tuple(args), err
 }
 
-func unmarshalUser(args ...interface{}) (interface{}, error) {
+func unmarshalUser(args ...interface{}) interface{} {
 	authResult := args[1].(*AuthResult)
-	err := authResult.row.Scan(&authResult.passwordHash, &authResult.sfaSecret,
-		&authResult.recoveryCodesRaw)
-	authResult.row.Close()
+	authResult.passwordHash = authResult.user.PasswordHash
+	authResult.sfaSecret = authResult.user.Sfa
+	authResult.recoveryCodesRaw = authResult.user.RecoveryCodes
 	authResult.recoveryCodes = strings.Split(authResult.recoveryCodesRaw, ",")
-	return gott.Tuple(args), err
+	return gott.Tuple(args)
 }
 
 func splitArgon(args ...interface{}) interface{} {
@@ -154,7 +153,7 @@ func updateSfa(args ...interface{}) (interface{}, error) {
 
 func createToken(args ...interface{}) (interface{}, error) {
 	authResult := args[1].(*AuthResult)
-	sessionIdRaw := make([]byte, 128) 
+	sessionIdRaw := make([]byte, 128)
 	rand.Read(sessionIdRaw)
 	sessionId := hex.EncodeToString(sessionIdRaw)
 	authResult.token = sessionId
@@ -164,7 +163,7 @@ func createToken(args ...interface{}) (interface{}, error) {
 func createSession(args ...interface{}) (interface{}, error) {
 	authData := args[0].(*AuthData)
 	authResult := args[1].(*AuthResult)
-	err := db.CreateSession(authData.username, authResult.token)
+	err := db.CreateSession(authData.username, authResult.token, false) // todo long session
 	return gott.Tuple(args), err
 }
 
@@ -173,8 +172,8 @@ func Login(username, password, sfa string, remember bool) (string, error) {
 		NewResult(gott.Tuple{&AuthData{username: username, password: password,
 			sfa: sfa, remember: remember}, &AuthResult{}}).
 		Bind(findUser).
-		//Clear old sessions
-		Bind(unmarshalUser).
+		//todo Clear old sessions
+		Map(unmarshalUser).
 		Bind(checkPassword).
 		Bind(checkSfa).
 		Bind(updateSfa).

+ 65 - 9
db/db.go

@@ -7,6 +7,7 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"time"
 
 	_ "github.com/mattn/go-sqlite3"
 )
@@ -19,6 +20,22 @@ func (e EmptyError) Error() string {
 	return e.message
 }
 
+type User struct {
+	Username      string
+	PasswordHash  string
+	Sfa           string
+	Avatar        []byte
+	AvatarSmall   []byte
+	IsAdmin       bool
+	RecoveryCodes string
+}
+
+type Session struct {
+	Id       string
+	Username string
+	Expiry   time.Time
+}
+
 func Migrate() error {
 	// todo migrations
 	db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
@@ -31,11 +48,11 @@ func Migrate() error {
 	if err != nil {
 		return err
 	}
-	_, err = db.Exec(`create table users(username text primary key, password text, sfa text, avatar blob, is_admin bool, recovery_codes text)`)
+	_, err = db.Exec(`create table users(username text primary key, password text, sfa text, avatar blob, avatar_small blob, is_admin bool, recovery_codes text)`)
 	if err != nil {
 		return err
 	}
-	_, err = db.Exec(`create table sessions(id text primary key, username text, created datetime, foreign key(username) references users(username))`)
+	_, err = db.Exec(`create table sessions(id text primary key, username text, expiry datetime, foreign key(username) references users(username))`)
 	if err != nil {
 		return err
 	}
@@ -76,22 +93,29 @@ func MakeAdmin(username string) error {
 	return nil
 }
 
-func GetUser(username string) (*sql.Rows, error) {
+func GetUser(username string) (*User, error) {
 	db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "DB open err\n")
 		return nil, err
 	}
 	defer db.Close()
-	rows, err := db.Query(`select password, sfa, recovery_codes from users where username = ?`, username)
+	rows, err := db.Query(`select password, sfa, recovery_codes, is_admin, avatar, avatar_small from users where username = ?`, username)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Select err %v\n", err)
 		return nil, err
 	}
+	defer rows.Close()
 	if !rows.Next() {
 		return nil, EmptyError{message: "User does not exist"}
 	}
-	return rows, nil
+	user := User{Username: username}
+	err = rows.Scan(&user.PasswordHash, &user.Sfa, &user.RecoveryCodes, &user.IsAdmin, &user.Avatar, &user.AvatarSmall)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Scan err %v\n", err)
+		return nil, err
+	}
+	return &user, nil
 }
 
 func UpdateRecoveryCodes(username, recoveryCodes string) error {
@@ -101,7 +125,7 @@ func UpdateRecoveryCodes(username, recoveryCodes string) error {
 		return err
 	}
 	defer db.Close()
-	
+
 	_, err = db.Exec(`update users set recovery_codes = ? where username = ?`, recoveryCodes, username)
 	if err != nil {
 		return err
@@ -110,18 +134,50 @@ func UpdateRecoveryCodes(username, recoveryCodes string) error {
 	return nil
 }
 
-func CreateSession(username, sessionId string) error {
+func CreateSession(username, sessionId string, long bool) error {
 	db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "DB open err\n")
 		return err
 	}
 	defer db.Close()
-	
-	_, err = db.Exec(`insert into sessions values(?, ?, datetime('now'))`, sessionId, username)
+
+	var length string
+	if long {
+		length = "30 days"
+	} else {
+		length = "1 hour"
+	}
+	_, err = db.Exec(`insert into sessions values(?, ?, datetime('now', '`+length+`'))`, sessionId, username)
 	if err != nil {
 		return err
 	}
 
 	return nil
 }
+
+func GetSession(token string) (*Session, error) {
+	db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "DB open err\n")
+		return nil, err
+	}
+	defer db.Close()
+
+	rows, err := db.Query(`select username, expiry from sessions where id = ?`, token)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Select err %v\n", err)
+		return nil, err
+	}
+	defer rows.Close()
+	if !rows.Next() {
+		return nil, EmptyError{message: "Session does not exist"}
+	}
+	session := Session{Id: token}
+	err = rows.Scan(&session.Username, &session.Expiry)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Scan err %v\n", err)
+		return nil, err
+	}
+	return &session, nil
+}

+ 7 - 3
front/html.go

@@ -47,7 +47,9 @@ func (d RenderData) GetErrorData(code int, kind string) string {
 	return i18n.GetErrorData(code, d.Strings, kind)
 }
 
-type HtmlRenderer struct{}
+type HtmlRenderer struct{
+	user accounts.User
+}
 
 func render(languages []language.Tag, data RenderData, file string) string {
 	i18n.LoadServerLangs()
@@ -68,8 +70,9 @@ func render(languages []language.Tag, data RenderData, file string) string {
 	return b.String()
 }
 
-func (HtmlRenderer) RenderFilm(film *tmdb.Film, languages []language.Tag) string {
+func (r HtmlRenderer) RenderFilm(film *tmdb.Film, languages []language.Tag) string {
 	data := RenderData{Data: film}
+	data.State.User = r.user
 	return render(languages, data, "film")
 }
 
@@ -82,8 +85,9 @@ func (HtmlRenderer) RenderSearch(tmdbResults *tmdb.SearchResults, inventaireResu
 	return render(languages, data, "search")
 }
 
-func (HtmlRenderer) RenderIndex(randomComedy string, languages []language.Tag) string {
+func (r HtmlRenderer) RenderIndex(randomComedy string, languages []language.Tag) string {
 	data := RenderData{Data: randomComedy}
+	data.State.User = r.user
 	return render(languages, data, "index")
 }
 

+ 3 - 2
front/renderer.go

@@ -1,6 +1,7 @@
 package front
 
 import (
+	"notabug.org/apiote/amuse/accounts"
 	"notabug.org/apiote/amuse/tmdb"
 	"notabug.org/apiote/amuse/wikidata"
 
@@ -28,10 +29,10 @@ type Renderer interface {
 	RenderLogin([]language.Tag, error) string
 }
 
-func NewRenderer(mimetype string) (Renderer, error) {
+func NewRenderer(mimetype string, user accounts.User) (Renderer, error) {
 	switch mimetype {
 	case "text/html":
-		return HtmlRenderer{}, nil
+		return HtmlRenderer{user: user}, nil
 	case "application/capnproto":
 		return CapnprotoRenderer{}, nil
 	default:

+ 29 - 2
libamuse/account.go

@@ -2,8 +2,35 @@ package libamuse
 
 import (
 	"notabug.org/apiote/amuse/accounts"
+	"notabug.org/apiote/amuse/db"
+
+	"errors"
+	"time"
+	"fmt"
+	"os"
 )
 
-func VerifyAuthToken(token string) (accounts.User, error) {
-	return accounts.User{}, nil
+func VerifyAuthToken(token accounts.Authentication) (accounts.User, error) {
+	if token.Token == "" {
+		return accounts.User{}, nil
+	}
+	session, err := db.GetSession(token.Token)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Get session err: %v", err)
+		return accounts.User{}, err
+	}
+	now := time.Now()
+	if session.Expiry.Before(now) {
+		return accounts.User{}, accounts.AuthError{Err: errors.New("Session expired")}
+	}
+	dbUser, err := db.GetUser(session.Username)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Get user err: %v", err)
+		return accounts.User{}, err
+	}
+	user := accounts.User{
+		Username: dbUser.Username,
+		IsAdmin: dbUser.IsAdmin,
+	}
+	return user, nil
 }

+ 13 - 1
libamuse/common.go

@@ -1,6 +1,7 @@
 package libamuse
 
 import (
+	"notabug.org/apiote/amuse/accounts"
 	"notabug.org/apiote/amuse/front"
 	"notabug.org/apiote/amuse/tmdb"
 	"notabug.org/apiote/amuse/wikidata"
@@ -16,6 +17,14 @@ type Data interface {
 	getMimeType() string
 }
 
+func verifyToken(args ...interface{}) (interface{}, error) {
+	requestData := args[0].(*RequestData)
+	result := args[1].(*Result)
+	user, err := VerifyAuthToken(requestData.auth)
+	result.user = user
+	return gott.Tuple(args), err
+}
+
 func createDbConnection(args ...interface{}) (interface{}, error) {
 	requestData := args[0].(*RequestData)
 	db, err := sql.Open("sqlite3", "./amuse.db")
@@ -43,7 +52,7 @@ func parseLanguage(args ...interface{}) (interface{}, error) {
 func createRenderer(args ...interface{}) (interface{}, error) {
 	data := args[0].(Data)
 	result := args[1].(*Result)
-	renderer, err := front.NewRenderer(data.getMimeType())
+	renderer, err := front.NewRenderer(data.getMimeType(), result.user)
 	result.renderer = renderer
 	return gott.Tuple(args), err
 }
@@ -70,15 +79,18 @@ func getBasedOn(args ...interface{}) (interface{}, error) {
 
 type RequestData struct {
 	id         string
+	etag       string
 	connection *sql.DB
 	language   string
 	mimetype   string
 	code       int
+	auth       accounts.Authentication
 }
 
 type Result struct {
 	languages []language.Tag
 	renderer  front.Renderer
+	user      accounts.User
 	result    interface{}
 	result2   interface{} // todo this is ugly -> to []interface{} with .result
 	page      string

+ 6 - 4
libamuse/film.go

@@ -2,10 +2,11 @@ package libamuse
 
 import (
 	"notabug.org/apiote/amuse/tmdb"
-
-	"notabug.org/apiote/gott"
+	"notabug.org/apiote/amuse/accounts"
 
 	"strconv"
+
+	"notabug.org/apiote/gott"
 )
 
 func getFilm(args ...interface{}) (interface{}, error) {
@@ -41,12 +42,13 @@ func renderFilm(args ...interface{}) interface{} {
 	return gott.Tuple(args)
 }
 
-func ShowFilm(id, language, mimetype string) (string, error) {
-	request := &RequestData{id: id, language: language, mimetype: mimetype}
+func ShowFilm(id, language, mimetype string, auth accounts.Authentication) (string, error) {
+	request := &RequestData{id: id, language: language, mimetype: mimetype, auth: auth}
 	r, err := gott.
 		NewResult(gott.Tuple{request, &Result{}}).
 		Bind(createDbConnection).
 		Bind(parseLanguage).
+		Bind(verifyToken).
 		Bind(getFilm).
 		Bind(getCollection).
 		Bind(getBasedOn).

+ 4 - 2
libamuse/index.go

@@ -1,6 +1,7 @@
 package libamuse
 
 import (
+	"notabug.org/apiote/amuse/accounts"
 	"notabug.org/apiote/amuse/tmdb"
 
 	"notabug.org/apiote/gott"
@@ -21,10 +22,11 @@ func renderIndex(args ...interface{}) interface{} {
 	return gott.Tuple(args)
 }
 
-func ShowIndex(language, mimetype string) (string, error) {
+func ShowIndex(language, mimetype string, authentication accounts.Authentication) (string, error) {
 	r, err := gott.
-		NewResult(gott.Tuple{&RequestData{language: language, mimetype: mimetype}, &Result{}}).
+		NewResult(gott.Tuple{&RequestData{language: language, mimetype: mimetype, auth: authentication}, &Result{}}).
 		Bind(parseLanguage).
+		Bind(verifyToken).
 		Bind(getRandomTitle).
 		Bind(createRenderer).
 		Map(renderIndex).

+ 0 - 0
libamuse/user.go


Some files were not shown because too many files changed in this diff