Merge pull request #24 from mattermost/auth

Initial Auth implementation
This commit is contained in:
Jesús Espino 2021-01-11 19:22:02 +01:00 committed by GitHub
commit 3d8a80fb11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 1839 additions and 222 deletions

View file

@ -36,6 +36,20 @@ server-linux-package: server-linux webapp
cd package && tar -czvf ../dist/octo-linux-amd64.tar.gz ${PACKAGE_FOLDER}
rm -rf package
server-single-user:
cd server; go build -o ../bin/octoserver ./main --single-user
server-mac-single-user:
mkdir -p bin/mac
cd server; env GOOS=darwin GOARCH=amd64 go build -o ../bin/mac/octoserver ./main --single-user
server-linux-single-user:
mkdir -p bin/linux
cd server; env GOOS=linux GOARCH=amd64 go build -o ../bin/linux/octoserver ./main --single-user
server-win-single-user:
cd server; env GOOS=windows GOARCH=amd64 go build -o ../bin/octoserver.exe ./main --single-user
generate:
cd server; go get -modfile=go.tools.mod github.com/golang/mock/mockgen
cd server; go get -modfile=go.tools.mod github.com/jteeuwen/go-bindata
@ -57,6 +71,9 @@ server-doc:
watch-server:
cd server; modd
watch-server-single-user:
cd server; env OCTOSERVER_ARGS=--single-user modd
webapp:
cd webapp; npm run pack

View file

@ -9,5 +9,8 @@
"webpath": "./webapp/pack",
"filespath": "./files",
"telemetry": true,
"webhook_update": []
"webhook_update": [],
"secret": "this-is-a-secret-string",
"session_expire_time": 2592000,
"session_refresh_time": 18000
}

View file

@ -11,7 +11,7 @@ import (
)
func runOctoTasks(ctx context.Context) {
cmd := exec.CommandContext(ctx, "./octoserver", "--monitorpid", strconv.FormatInt(int64(os.Getpid()), 10))
cmd := exec.CommandContext(ctx, "./octoserver", "--monitorpid", strconv.FormatInt(int64(os.Getpid()), 10), "--single-user")
cmd.Stdout = os.Stdout
err := cmd.Run()
if err != nil {

View file

@ -80,7 +80,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
NSLog("pid: \(pid)")
let serverProcess = Process()
serverProcess.currentDirectoryPath = cwdUrl.path
serverProcess.arguments = ["-monitorpid", "\(pid)", "-port", "\(serverPort)"]
serverProcess.arguments = ["-monitorpid", "\(pid)", "-port", "\(serverPort)", "--single-user"]
serverProcess.launchPath = executablePath
serverProcess.launch()
self.serverProcess = serverProcess

View file

@ -10,6 +10,7 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/mattermost/mattermost-octo-tasks/server/app"
@ -21,10 +22,11 @@ import (
type API struct {
appBuilder func() *app.App
singleUser bool
}
func NewAPI(appBuilder func() *app.App) *API {
return &API{appBuilder: appBuilder}
func NewAPI(appBuilder func() *app.App, singleUser bool) *API {
return &API{appBuilder: appBuilder, singleUser: singleUser}
}
func (a *API) app() *app.App {
@ -32,16 +34,22 @@ func (a *API) app() *app.App {
}
func (a *API) RegisterRoutes(r *mux.Router) {
r.HandleFunc("/api/v1/blocks", a.handleGetBlocks).Methods("GET")
r.HandleFunc("/api/v1/blocks", a.handlePostBlocks).Methods("POST")
r.HandleFunc("/api/v1/blocks/{blockID}", a.handleDeleteBlock).Methods("DELETE")
r.HandleFunc("/api/v1/blocks/{blockID}/subtree", a.handleGetSubTree).Methods("GET")
r.HandleFunc("/api/v1/blocks", a.sessionRequired(a.handleGetBlocks)).Methods("GET")
r.HandleFunc("/api/v1/blocks", a.sessionRequired(a.handlePostBlocks)).Methods("POST")
r.HandleFunc("/api/v1/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE")
r.HandleFunc("/api/v1/blocks/{blockID}/subtree", a.sessionRequired(a.handleGetSubTree)).Methods("GET")
r.HandleFunc("/api/v1/files", a.handleUploadFile).Methods("POST")
r.HandleFunc("/files/{filename}", a.handleServeFile).Methods("GET")
r.HandleFunc("/api/v1/users/me", a.sessionRequired(a.handleGetMe)).Methods("GET")
r.HandleFunc("/api/v1/users/{userID}", a.sessionRequired(a.handleGetUser)).Methods("GET")
r.HandleFunc("/api/v1/blocks/export", a.handleExport).Methods("GET")
r.HandleFunc("/api/v1/blocks/import", a.handleImport).Methods("POST")
r.HandleFunc("/api/v1/login", a.handleLogin).Methods("POST")
r.HandleFunc("/api/v1/register", a.handleRegister).Methods("POST")
r.HandleFunc("/api/v1/files", a.sessionRequired(a.handleUploadFile)).Methods("POST")
r.HandleFunc("/files/{filename}", a.sessionRequired(a.handleServeFile)).Methods("GET")
r.HandleFunc("/api/v1/blocks/export", a.sessionRequired(a.handleExport)).Methods("GET")
r.HandleFunc("/api/v1/blocks/import", a.sessionRequired(a.handleImport)).Methods("POST")
}
func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
@ -133,6 +141,65 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
jsonStringResponse(w, http.StatusOK, "{}")
}
func (a *API) handleGetUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
userID := vars["userID"]
user, err := a.app().GetUser(userID)
if err != nil {
log.Printf(`ERROR: %v`, r)
errorResponse(w, http.StatusInternalServerError, nil)
return
}
userData, err := json.Marshal(user)
if err != nil {
log.Printf(`ERROR: %v`, r)
errorResponse(w, http.StatusInternalServerError, nil)
return
}
jsonStringResponse(w, http.StatusOK, string(userData))
}
func (a *API) handleGetMe(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session := ctx.Value("session").(*model.Session)
var user *model.User
var err error
if session.UserID == "single-user" {
now := time.Now().Unix()
user = &model.User{
ID: "single-user",
Username: "single-user",
Email: "single-user",
CreateAt: now,
UpdateAt: now,
}
} else {
user, err = a.app().GetUser(session.UserID)
if err != nil {
log.Printf(`ERROR: %v`, r)
errorResponse(w, http.StatusInternalServerError, nil)
return
}
}
userData, err := json.Marshal(user)
if err != nil {
log.Printf(`ERROR: %v`, r)
errorResponse(w, http.StatusInternalServerError, nil)
return
}
jsonStringResponse(w, http.StatusOK, string(userData))
}
func (a *API) handleDeleteBlock(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
blockID := vars["blockID"]
@ -354,6 +421,7 @@ func jsonBytesResponse(w http.ResponseWriter, code int, json []byte) {
func errorResponse(w http.ResponseWriter, code int, message map[string]string) {
log.Printf("%d ERROR", code)
w.Header().Set("Content-Type", "application/json")
data, err := json.Marshal(message)
if err != nil {
data = []byte("{}")

138
server/api/auth.go Normal file
View file

@ -0,0 +1,138 @@
package api
import (
"context"
"encoding/json"
"errors"
"io/ioutil"
"log"
"net/http"
"strings"
"time"
"github.com/mattermost/mattermost-octo-tasks/server/model"
"github.com/mattermost/mattermost-octo-tasks/server/services/auth"
)
type LoginData struct {
Type string `json:"type"`
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
MfaToken string `json:"mfa_token"`
}
type RegisterData struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
}
func (rd *RegisterData) IsValid() error {
if rd.Username == "" {
return errors.New("Username is required")
}
if rd.Email == "" {
return errors.New("Email is required")
}
if !strings.Contains(rd.Email, "@") {
return errors.New("Invalid email format")
}
if !strings.Contains(rd.Password, "") {
return errors.New("Password is required")
}
return nil
}
func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) {
requestBody, err := ioutil.ReadAll(r.Body)
if err != nil {
errorResponse(w, http.StatusInternalServerError, nil)
return
}
var loginData LoginData
err = json.Unmarshal(requestBody, &loginData)
if err != nil {
errorResponse(w, http.StatusInternalServerError, nil)
return
}
if loginData.Type == "normal" {
token, err := a.app().Login(loginData.Username, loginData.Email, loginData.Password, loginData.MfaToken)
if err != nil {
errorResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
json, err := json.Marshal(map[string]string{"token": token})
if err != nil {
log.Printf(`ERROR json.Marshal: %v`, r)
errorResponse(w, http.StatusInternalServerError, nil)
return
}
jsonBytesResponse(w, http.StatusOK, json)
return
}
errorResponse(w, http.StatusInternalServerError, map[string]string{"error": "Unknown login type"})
return
}
func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) {
requestBody, err := ioutil.ReadAll(r.Body)
if err != nil {
errorResponse(w, http.StatusInternalServerError, nil)
return
}
var registerData RegisterData
err = json.Unmarshal(requestBody, &registerData)
if err != nil {
errorResponse(w, http.StatusInternalServerError, nil)
return
}
if err = registerData.IsValid(); err != nil {
errorResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
err = a.app().RegisterUser(registerData.Username, registerData.Email, registerData.Password)
if err != nil {
errorResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
jsonBytesResponse(w, http.StatusOK, nil)
return
}
func (a *API) sessionRequired(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf(`Single User: %v`, a.singleUser)
if a.singleUser {
now := time.Now().Unix()
session := &model.Session{
ID: "single-user",
Token: "single-user",
UserID: "single-user",
CreateAt: now,
UpdateAt: now,
}
ctx := context.WithValue(r.Context(), "session", session)
handler(w, r.WithContext(ctx))
return
}
token, _ := auth.ParseAuthTokenFromRequest(r)
session, err := a.app().GetSession(token)
if err != nil {
errorResponse(w, http.StatusUnauthorized, map[string]string{"error": err.Error()})
return
}
ctx := context.WithValue(r.Context(), "session", session)
handler(w, r.WithContext(ctx))
}
}

121
server/app/auth.go Normal file
View file

@ -0,0 +1,121 @@
package app
import (
"log"
"time"
"github.com/google/uuid"
"github.com/mattermost/mattermost-octo-tasks/server/model"
"github.com/mattermost/mattermost-octo-tasks/server/services/auth"
"github.com/pkg/errors"
)
// GetSession Get a user active session and refresh the session if is needed
func (a *App) GetSession(token string) (*model.Session, error) {
session, err := a.store.GetSession(token, a.config.SessionExpireTime)
if err != nil {
return nil, errors.Wrap(err, "unable to get the session for the token")
}
if session.UpdateAt < (time.Now().Unix() - a.config.SessionRefreshTime) {
a.store.RefreshSession(session)
}
return session, nil
}
// GetUser Get an existing active user by id
func (a *App) GetUser(ID string) (*model.User, error) {
user, err := a.store.GetUserById(ID)
if err != nil {
return nil, errors.Wrap(err, "unable to get the session for the token")
}
return user, nil
}
// Login create a new user session if the authentication data is valid
func (a *App) Login(username string, email string, password string, mfaToken string) (string, error) {
var user *model.User
if username != "" {
var err error
user, err = a.store.GetUserByUsername(username)
if err != nil {
return "", errors.Wrap(err, "invalid username or password")
}
}
if user == nil && email != "" {
var err error
user, err = a.store.GetUserByEmail(email)
if err != nil {
return "", errors.Wrap(err, "invalid username or password")
}
}
if user == nil {
return "", errors.New("invalid username or password")
}
if !auth.ComparePassword(user.Password, password) {
log.Printf("Not valid passowrd. %s (%s)\n", password, user.Password)
return "", errors.New("invalid username or password")
}
session := model.Session{
ID: uuid.New().String(),
Token: uuid.New().String(),
UserID: user.ID,
Props: map[string]interface{}{},
}
err := a.store.CreateSession(&session)
if err != nil {
return "", errors.Wrap(err, "unable to create session")
}
// TODO: MFA verification
return session.Token, nil
}
// RegisterUser create a new user if the provided data is valid
func (a *App) RegisterUser(username string, email string, password string) error {
var user *model.User
if username != "" {
var err error
user, err = a.store.GetUserByUsername(username)
if err == nil && user != nil {
return errors.Wrap(err, "The username already exists")
}
}
if user == nil && email != "" {
var err error
user, err = a.store.GetUserByEmail(email)
if err == nil && user != nil {
return errors.Wrap(err, "The email already exists")
}
}
// TODO: Move this into the config
passwordSettings := auth.PasswordSettings{
MinimumLength: 6,
}
err := auth.IsPasswordValid(password, passwordSettings)
if err != nil {
return errors.Wrap(err, "Invalid password")
}
err = a.store.CreateUser(&model.User{
ID: uuid.New().String(),
Username: username,
Email: email,
Password: auth.HashPassword(password),
MfaSecret: "",
AuthService: "",
AuthData: "",
Props: map[string]interface{}{},
})
if err != nil {
return errors.Wrap(err, "Unable to create the new user")
}
return nil
}

View file

@ -4,6 +4,7 @@ go 1.15
require (
github.com/Masterminds/squirrel v1.4.0
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-ldap/ldap v3.0.3+incompatible // indirect
github.com/golang-migrate/migrate v3.5.4+incompatible
github.com/golang-migrate/migrate/v4 v4.13.0
@ -20,10 +21,12 @@ require (
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/nicksnyder/go-i18n v1.10.1 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pkg/errors v0.9.1
github.com/rudderlabs/analytics-go v3.2.1+incompatible
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.6.1
go.uber.org/zap v1.15.0
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
golang.org/x/tools v0.0.0-20201017001424-6003fad69a88 // indirect
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect

View file

@ -186,6 +186,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3 h1:AqeKSZIG/NIC75MNQlPy/LM3LxfpLwahICJBHwSMFNc=
github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3/go.mod h1:hEfFauPHz7+NnjR/yHJGhrKo1Za+zStgwUETx3yzqgY=

View file

@ -28,7 +28,7 @@ func getTestConfig() *config.Configuration {
func SetupTestHelper() *TestHelper {
th := &TestHelper{}
srv, err := server.New(getTestConfig())
srv, err := server.New(getTestConfig(), true)
if err != nil {
panic(err)
}

View file

@ -56,8 +56,14 @@ func main() {
// Command line args
pMonitorPid := flag.Int("monitorpid", -1, "a process ID")
pPort := flag.Int("port", config.Port, "the port number")
pSingleUser := flag.Bool("single-user", false, "single user mode")
flag.Parse()
singleUser := false
if pSingleUser != nil {
singleUser = *pSingleUser
}
if pMonitorPid != nil && *pMonitorPid > 0 {
monitorPid(*pMonitorPid)
}
@ -68,7 +74,7 @@ func main() {
config.Port = *pPort
}
server, err := server.New(config)
server, err := server.New(config, singleUser)
if err != nil {
log.Fatal("ListenAndServeTLS: ", err)
}

View file

@ -1,4 +1,4 @@
**/*.go !**/*_test.go {
prep: go build -o ../bin/octoserver ./main
daemon +sigterm: cd .. && ./bin/octoserver
daemon +sigterm: cd .. && ./bin/octoserver $OCTOSERVER_ARGS
}

24
server/model/user.go Normal file
View file

@ -0,0 +1,24 @@
package model
type User struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"-"`
MfaSecret string `json:"-"`
AuthService string `json:"-"`
AuthData string `json:"-"`
Props map[string]interface{} `json:"props"`
CreateAt int64 `json:"create_at,omitempty"`
UpdateAt int64 `json:"update_at,omitempty"`
DeleteAt int64 `json:"delete_at"`
}
type Session struct {
ID string `json:"id"`
Token string `json:"token"`
UserID string `json:"user_id"`
Props map[string]interface{} `json:"props"`
CreateAt int64 `json:"create_at,omitempty"`
UpdateAt int64 `json:"update_at,omitempty"`
}

View file

@ -6,6 +6,7 @@ import (
"os"
"os/signal"
"runtime"
"time"
"github.com/google/uuid"
"go.uber.org/zap"
@ -13,6 +14,7 @@ import (
"github.com/mattermost/mattermost-octo-tasks/server/api"
"github.com/mattermost/mattermost-octo-tasks/server/app"
"github.com/mattermost/mattermost-octo-tasks/server/services/config"
"github.com/mattermost/mattermost-octo-tasks/server/services/scheduler"
"github.com/mattermost/mattermost-octo-tasks/server/services/store"
"github.com/mattermost/mattermost-octo-tasks/server/services/store/sqlstore"
"github.com/mattermost/mattermost-octo-tasks/server/services/telemetry"
@ -26,16 +28,17 @@ import (
const currentVersion = "0.0.1"
type Server struct {
config *config.Configuration
wsServer *ws.Server
webServer *web.Server
store store.Store
filesBackend filesstore.FileBackend
telemetry *telemetry.Service
logger *zap.Logger
config *config.Configuration
wsServer *ws.Server
webServer *web.Server
store store.Store
filesBackend filesstore.FileBackend
telemetry *telemetry.Service
logger *zap.Logger
cleanUpSessionsTask *scheduler.ScheduledTask
}
func New(cfg *config.Configuration) (*Server, error) {
func New(cfg *config.Configuration, singleUser bool) (*Server, error) {
logger, err := zap.NewProduction()
if err != nil {
return nil, err
@ -63,7 +66,7 @@ func New(cfg *config.Configuration) (*Server, error) {
webhookClient := webhook.NewClient(cfg)
appBuilder := func() *app.App { return app.New(cfg, store, wsServer, filesBackend, webhookClient) }
api := api.NewAPI(appBuilder)
api := api.NewAPI(appBuilder, singleUser)
webServer := web.NewServer(cfg.WebPath, cfg.Port, cfg.UseSSL)
webServer.AddRoutes(wsServer)
@ -130,6 +133,11 @@ func (s *Server) Start() error {
if err := s.webServer.Start(); err != nil {
return err
}
s.cleanUpSessionsTask = scheduler.CreateRecurringTask("cleanUpSessions", func() {
if err := s.store.CleanUpSessions(s.config.SessionExpireTime); err != nil {
s.logger.Error("Unable to clean up the sessions", zap.Error(err))
}
}, 10*time.Minute)
return nil
}
@ -139,6 +147,10 @@ func (s *Server) Shutdown() error {
return err
}
if s.cleanUpSessionsTask != nil {
s.cleanUpSessionsTask.Cancel()
}
return s.store.Shutdown()
}

View file

@ -0,0 +1,125 @@
package auth
import (
"fmt"
"math/rand"
"strings"
"time"
"golang.org/x/crypto/bcrypt"
)
var passwordRandomSource = rand.NewSource(time.Now().Unix())
const (
PasswordMaximumLength = 64
PasswordSpecialChars = "!\"\\#$%&'()*+,-./:;<=>?@[]^_`|~"
PasswordNumbers = "0123456789"
PasswordUpperCaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
PasswordLowerCaseLetters = "abcdefghijklmnopqrstuvwxyz"
PasswordAllChars = PasswordSpecialChars + PasswordNumbers + PasswordUpperCaseLetters + PasswordLowerCaseLetters
InvalidLowercasePassword = "lowercase"
InvalidMinLengthPassword = "min-length"
InvalidMaxLengthPassword = "max-length"
InvalidNumberPassword = "number"
InvalidUppercasePassword = "uppercase"
InvalidSymbolPassword = "symbol"
)
// HashPassword generates a hash using the bcrypt.GenerateFromPassword
func HashPassword(password string) string {
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
if err != nil {
panic(err)
}
return string(hash)
}
// ComparePassword compares the hash
func ComparePassword(hash string, password string) bool {
if len(password) == 0 || len(hash) == 0 {
return false
}
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
func GeneratePassword(minimumLength int) string {
r := rand.New(passwordRandomSource)
// Make sure we are guaranteed at least one of each type to meet any possible password complexity requirements.
password := string([]rune(PasswordUpperCaseLetters)[r.Intn(len(PasswordUpperCaseLetters))]) +
string([]rune(PasswordNumbers)[r.Intn(len(PasswordNumbers))]) +
string([]rune(PasswordLowerCaseLetters)[r.Intn(len(PasswordLowerCaseLetters))]) +
string([]rune(PasswordSpecialChars)[r.Intn(len(PasswordSpecialChars))])
for len(password) < minimumLength {
i := r.Intn(len(PasswordAllChars))
password = password + string([]rune(PasswordAllChars)[i])
}
return password
}
type InvalidPasswordError struct {
FailingCriterias []string
}
func (ipe *InvalidPasswordError) Error() string {
return fmt.Sprintf("invalid password, failing criterias: %s", strings.Join(ipe.FailingCriterias, ", "))
}
type PasswordSettings struct {
MinimumLength int
Lowercase bool
Number bool
Uppercase bool
Symbol bool
}
func IsPasswordValid(password string, settings PasswordSettings) error {
err := &InvalidPasswordError{
FailingCriterias: []string{},
}
if len(password) < settings.MinimumLength {
err.FailingCriterias = append(err.FailingCriterias, InvalidMinLengthPassword)
}
if len(password) > PasswordMaximumLength {
err.FailingCriterias = append(err.FailingCriterias, InvalidMaxLengthPassword)
}
if settings.Lowercase {
if !strings.ContainsAny(password, PasswordLowerCaseLetters) {
err.FailingCriterias = append(err.FailingCriterias, InvalidLowercasePassword)
}
}
if settings.Uppercase {
if !strings.ContainsAny(password, PasswordUpperCaseLetters) {
err.FailingCriterias = append(err.FailingCriterias, InvalidUppercasePassword)
}
}
if settings.Number {
if !strings.ContainsAny(password, PasswordNumbers) {
err.FailingCriterias = append(err.FailingCriterias, InvalidNumberPassword)
}
}
if settings.Symbol {
if !strings.ContainsAny(password, PasswordSpecialChars) {
err.FailingCriterias = append(err.FailingCriterias, InvalidSymbolPassword)
}
}
if len(err.FailingCriterias) > 0 {
return err
}
return nil
}

View file

@ -0,0 +1,165 @@
package auth
import (
"math/rand"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPasswordHash(t *testing.T) {
hash := HashPassword("Test")
assert.True(t, ComparePassword(hash, "Test"), "Passwords don't match")
assert.False(t, ComparePassword(hash, "Test2"), "Passwords should not have matched")
}
func TestGeneratePassword(t *testing.T) {
passwordRandomSource = rand.NewSource(12345)
t.Run("Should be the minimum length or 4, whichever is less", func(t *testing.T) {
password1 := GeneratePassword(5)
assert.Len(t, password1, 5)
password2 := GeneratePassword(10)
assert.Len(t, password2, 10)
password3 := GeneratePassword(1)
assert.Len(t, password3, 4)
})
t.Run("Should contain at least one of symbols, upper case, lower case and numbers", func(t *testing.T) {
password := GeneratePassword(4)
require.Len(t, password, 4)
assert.Contains(t, []rune(PasswordUpperCaseLetters), []rune(password)[0])
assert.Contains(t, []rune(PasswordNumbers), []rune(password)[1])
assert.Contains(t, []rune(PasswordLowerCaseLetters), []rune(password)[2])
assert.Contains(t, []rune(PasswordSpecialChars), []rune(password)[3])
})
}
func TestIsPasswordValidWithSettings(t *testing.T) {
for name, tc := range map[string]struct {
Password string
Settings PasswordSettings
ExpectedFailingCriterias []string
}{
"Short": {
Password: strings.Repeat("x", 3),
Settings: PasswordSettings{
MinimumLength: 3,
Lowercase: false,
Uppercase: false,
Number: false,
Symbol: false,
},
},
"Long": {
Password: strings.Repeat("x", PasswordMaximumLength),
Settings: PasswordSettings{
MinimumLength: 3,
Lowercase: false,
Uppercase: false,
Number: false,
Symbol: false,
},
},
"TooShort": {
Password: strings.Repeat("x", 2),
Settings: PasswordSettings{
MinimumLength: 3,
Lowercase: false,
Uppercase: false,
Number: false,
Symbol: false,
},
ExpectedFailingCriterias: []string{"min-length"},
},
"TooLong": {
Password: strings.Repeat("x", PasswordMaximumLength+1),
Settings: PasswordSettings{
MinimumLength: 3,
Lowercase: false,
Uppercase: false,
Number: false,
Symbol: false,
},
ExpectedFailingCriterias: []string{"max-length"},
},
"MissingLower": {
Password: "AAAAAAAAAAASD123!@#",
Settings: PasswordSettings{
MinimumLength: 3,
Lowercase: true,
Uppercase: false,
Number: false,
Symbol: false,
},
ExpectedFailingCriterias: []string{"lowercase"},
},
"MissingUpper": {
Password: "aaaaaaaaaaaaasd123!@#",
Settings: PasswordSettings{
MinimumLength: 3,
Uppercase: true,
Lowercase: false,
Number: false,
Symbol: false,
},
ExpectedFailingCriterias: []string{"uppercase"},
},
"MissingNumber": {
Password: "asasdasdsadASD!@#",
Settings: PasswordSettings{
MinimumLength: 3,
Number: true,
Lowercase: false,
Uppercase: false,
Symbol: false,
},
ExpectedFailingCriterias: []string{"number"},
},
"MissingSymbol": {
Password: "asdasdasdasdasdASD123",
Settings: PasswordSettings{
MinimumLength: 3,
Symbol: true,
Lowercase: false,
Uppercase: false,
Number: false,
},
ExpectedFailingCriterias: []string{"symbol"},
},
"MissingMultiple": {
Password: "asdasdasdasdasdasd",
Settings: PasswordSettings{
MinimumLength: 3,
Lowercase: true,
Uppercase: true,
Number: true,
Symbol: true,
},
ExpectedFailingCriterias: []string{"uppercase", "number", "symbol"},
},
"Everything": {
Password: "asdASD!@#123",
Settings: PasswordSettings{
MinimumLength: 3,
Lowercase: true,
Uppercase: true,
Number: true,
Symbol: true,
},
},
} {
t.Run(name, func(t *testing.T) {
err := IsPasswordValid(tc.Password, tc.Settings)
if len(tc.ExpectedFailingCriterias) == 0 {
assert.NoError(t, err)
} else {
require.Error(t, err)
assert.Equal(t, tc.ExpectedFailingCriterias, err.(*InvalidPasswordError).FailingCriterias)
}
})
}
}

View file

@ -0,0 +1,67 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package auth
import (
"net/http"
"strings"
)
const (
HEADER_TOKEN = "token"
HEADER_AUTH = "Authorization"
HEADER_BEARER = "BEARER"
SESSION_COOKIE_TOKEN = "OCTOTASKSAUTHTOKEN"
)
type TokenLocation int
const (
TokenLocationNotFound TokenLocation = iota
TokenLocationHeader
TokenLocationCookie
TokenLocationQueryString
)
func (tl TokenLocation) String() string {
switch tl {
case TokenLocationNotFound:
return "Not Found"
case TokenLocationHeader:
return "Header"
case TokenLocationCookie:
return "Cookie"
case TokenLocationQueryString:
return "QueryString"
default:
return "Unknown"
}
}
func ParseAuthTokenFromRequest(r *http.Request) (string, TokenLocation) {
authHeader := r.Header.Get(HEADER_AUTH)
// Attempt to parse the token from the cookie
if cookie, err := r.Cookie(SESSION_COOKIE_TOKEN); err == nil {
return cookie.Value, TokenLocationCookie
}
// Parse the token from the header
if len(authHeader) > 6 && strings.ToUpper(authHeader[0:6]) == HEADER_BEARER {
// Default session token
return authHeader[7:], TokenLocationHeader
}
if len(authHeader) > 5 && strings.ToLower(authHeader[0:5]) == HEADER_TOKEN {
// OAuth token
return authHeader[6:], TokenLocationHeader
}
// Attempt to parse token out of the query string
if token := r.URL.Query().Get("access_token"); token != "" {
return token, TokenLocationQueryString
}
return "", TokenLocationNotFound
}

View file

@ -0,0 +1,48 @@
package auth
import (
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/stretchr/testify/require"
)
func TestParseAuthTokenFromRequest(t *testing.T) {
cases := []struct {
header string
cookie string
query string
expectedToken string
expectedLocation TokenLocation
}{
{"", "", "", "", TokenLocationNotFound},
{"token mytoken", "", "", "mytoken", TokenLocationHeader},
{"BEARER mytoken", "", "", "mytoken", TokenLocationHeader},
{"", "mytoken", "", "mytoken", TokenLocationCookie},
{"", "", "mytoken", "mytoken", TokenLocationQueryString},
}
for testnum, tc := range cases {
pathname := "/test/here"
if tc.query != "" {
pathname += "?access_token=" + tc.query
}
req := httptest.NewRequest("GET", pathname, nil)
if tc.header != "" {
req.Header.Add(HEADER_AUTH, tc.header)
}
if tc.cookie != "" {
req.AddCookie(&http.Cookie{
Name: SESSION_COOKIE_TOKEN,
Value: tc.cookie,
})
}
token, location := ParseAuthTokenFromRequest(req)
require.Equal(t, tc.expectedToken, token, "Wrong token on test "+strconv.Itoa(testnum))
require.Equal(t, tc.expectedLocation, location, "Wrong location on test "+strconv.Itoa(testnum))
}
}

View file

@ -13,15 +13,18 @@ const (
// Configuration is the app configuration stored in a json file.
type Configuration struct {
ServerRoot string `json:"serverRoot" mapstructure:"serverRoot"`
Port int `json:"port" mapstructure:"port"`
DBType string `json:"dbtype" mapstructure:"dbtype"`
DBConfigString string `json:"dbconfig" mapstructure:"dbconfig"`
UseSSL bool `json:"useSSL" mapstructure:"useSSL"`
WebPath string `json:"webpath" mapstructure:"webpath"`
FilesPath string `json:"filespath" mapstructure:"filespath"`
Telemetry bool `json:"telemetry" mapstructure:"telemetry"`
WebhookUpdate []string `json:"webhook_update" mapstructure:"webhook_update"`
ServerRoot string `json:"serverRoot" mapstructure:"serverRoot"`
Port int `json:"port" mapstructure:"port"`
DBType string `json:"dbtype" mapstructure:"dbtype"`
DBConfigString string `json:"dbconfig" mapstructure:"dbconfig"`
UseSSL bool `json:"useSSL" mapstructure:"useSSL"`
WebPath string `json:"webpath" mapstructure:"webpath"`
FilesPath string `json:"filespath" mapstructure:"filespath"`
Telemetry bool `json:"telemetry" mapstructure:"telemetry"`
WebhookUpdate []string `json:"webhook_update" mapstructure:"webhook_update"`
Secret string `json:"secret" mapstructure:"secret"`
SessionExpireTime int64 `json:"session_expire_time" mapstructure:"session_expire_time"`
SessionRefreshTime int64 `json:"session_refresh_time" mapstructure:"session_refresh_time"`
}
// ReadConfigFile read the configuration from the filesystem.
@ -36,6 +39,8 @@ func ReadConfigFile() (*Configuration, error) {
viper.SetDefault("WebPath", "./pack")
viper.SetDefault("FilesPath", "./files")
viper.SetDefault("WebhookUpdate", nil)
viper.SetDefault("SessionExpireTime", 60*60*24*30) // 30 days session lifetime
viper.SetDefault("SessionRefreshTime", 60*60*5) // 5 minutes session refresh
err := viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file

View file

@ -33,6 +33,48 @@ func (m *MockStore) EXPECT() *MockStoreMockRecorder {
return m.recorder
}
// CleanUpSessions mocks base method
func (m *MockStore) CleanUpSessions(arg0 int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CleanUpSessions", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// CleanUpSessions indicates an expected call of CleanUpSessions
func (mr *MockStoreMockRecorder) CleanUpSessions(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanUpSessions", reflect.TypeOf((*MockStore)(nil).CleanUpSessions), arg0)
}
// CreateSession mocks base method
func (m *MockStore) CreateSession(arg0 *model.Session) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSession", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// CreateSession indicates an expected call of CreateSession
func (mr *MockStoreMockRecorder) CreateSession(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSession", reflect.TypeOf((*MockStore)(nil).CreateSession), arg0)
}
// CreateUser mocks base method
func (m *MockStore) CreateUser(arg0 *model.User) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateUser", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// CreateUser indicates an expected call of CreateUser
func (mr *MockStoreMockRecorder) CreateUser(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockStore)(nil).CreateUser), arg0)
}
// DeleteBlock mocks base method
func (m *MockStore) DeleteBlock(arg0 string) error {
m.ctrl.T.Helper()
@ -47,6 +89,20 @@ func (mr *MockStoreMockRecorder) DeleteBlock(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBlock", reflect.TypeOf((*MockStore)(nil).DeleteBlock), arg0)
}
// DeleteSession mocks base method
func (m *MockStore) DeleteSession(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteSession", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteSession indicates an expected call of DeleteSession
func (mr *MockStoreMockRecorder) DeleteSession(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSession", reflect.TypeOf((*MockStore)(nil).DeleteSession), arg0)
}
// GetAllBlocks mocks base method
func (m *MockStore) GetAllBlocks() ([]model.Block, error) {
m.ctrl.T.Helper()
@ -122,6 +178,21 @@ func (mr *MockStoreMockRecorder) GetParentID(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParentID", reflect.TypeOf((*MockStore)(nil).GetParentID), arg0)
}
// GetSession mocks base method
func (m *MockStore) GetSession(arg0 string, arg1 int64) (*model.Session, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSession", arg0, arg1)
ret0, _ := ret[0].(*model.Session)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSession indicates an expected call of GetSession
func (mr *MockStoreMockRecorder) GetSession(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSession", reflect.TypeOf((*MockStore)(nil).GetSession), arg0, arg1)
}
// GetSubTree2 mocks base method
func (m *MockStore) GetSubTree2(arg0 string) ([]model.Block, error) {
m.ctrl.T.Helper()
@ -167,6 +238,51 @@ func (mr *MockStoreMockRecorder) GetSystemSettings() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSystemSettings", reflect.TypeOf((*MockStore)(nil).GetSystemSettings))
}
// GetUserByEmail mocks base method
func (m *MockStore) GetUserByEmail(arg0 string) (*model.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserByEmail", arg0)
ret0, _ := ret[0].(*model.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserByEmail indicates an expected call of GetUserByEmail
func (mr *MockStoreMockRecorder) GetUserByEmail(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByEmail", reflect.TypeOf((*MockStore)(nil).GetUserByEmail), arg0)
}
// GetUserById mocks base method
func (m *MockStore) GetUserById(arg0 string) (*model.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserById", arg0)
ret0, _ := ret[0].(*model.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserById indicates an expected call of GetUserById
func (mr *MockStoreMockRecorder) GetUserById(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserById", reflect.TypeOf((*MockStore)(nil).GetUserById), arg0)
}
// GetUserByUsername mocks base method
func (m *MockStore) GetUserByUsername(arg0 string) (*model.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserByUsername", arg0)
ret0, _ := ret[0].(*model.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserByUsername indicates an expected call of GetUserByUsername
func (mr *MockStoreMockRecorder) GetUserByUsername(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByUsername", reflect.TypeOf((*MockStore)(nil).GetUserByUsername), arg0)
}
// InsertBlock mocks base method
func (m *MockStore) InsertBlock(arg0 model.Block) error {
m.ctrl.T.Helper()
@ -181,6 +297,20 @@ func (mr *MockStoreMockRecorder) InsertBlock(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertBlock", reflect.TypeOf((*MockStore)(nil).InsertBlock), arg0)
}
// RefreshSession mocks base method
func (m *MockStore) RefreshSession(arg0 *model.Session) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RefreshSession", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// RefreshSession indicates an expected call of RefreshSession
func (mr *MockStoreMockRecorder) RefreshSession(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshSession", reflect.TypeOf((*MockStore)(nil).RefreshSession), arg0)
}
// SetSystemSetting mocks base method
func (m *MockStore) SetSystemSetting(arg0, arg1 string) error {
m.ctrl.T.Helper()
@ -208,3 +338,31 @@ func (mr *MockStoreMockRecorder) Shutdown() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockStore)(nil).Shutdown))
}
// UpdateSession mocks base method
func (m *MockStore) UpdateSession(arg0 *model.Session) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateSession", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateSession indicates an expected call of UpdateSession
func (mr *MockStoreMockRecorder) UpdateSession(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSession", reflect.TypeOf((*MockStore)(nil).UpdateSession), arg0)
}
// UpdateUser mocks base method
func (m *MockStore) UpdateUser(arg0 *model.User) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateUser", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateUser indicates an expected call of UpdateUser
func (mr *MockStoreMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockStore)(nil).UpdateUser), arg0)
}

View file

@ -1,11 +1,13 @@
// Code generated by go-bindata. DO NOT EDIT.
// sources:
// templates/templates.json
// templates/templates.json (22.926kB)
package initializations
import (
"bytes"
"compress/gzip"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
@ -18,7 +20,7 @@ import (
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
return nil, fmt.Errorf("read %q: %w", name, err)
}
var buf bytes.Buffer
@ -26,7 +28,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
return nil, fmt.Errorf("read %q: %w", name, err)
}
if clErr != nil {
return nil, err
@ -36,8 +38,9 @@ func bindataRead(data []byte, name string) ([]byte, error) {
}
type asset struct {
bytes []byte
info os.FileInfo
bytes []byte
info os.FileInfo
digest [sha256.Size]byte
}
type bindataFileInfo struct {
@ -81,8 +84,8 @@ func templatesJson() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "templates.json", size: 22926, mode: os.FileMode(420), modTime: time.Unix(1609798890, 0)}
a := &asset{bytes: bytes, info: info}
info := bindataFileInfo{name: "templates.json", size: 22926, mode: os.FileMode(0644), modTime: time.Unix(1610369786, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x3, 0x15, 0x49, 0xe, 0xb2, 0xe7, 0x7, 0xd0, 0x6e, 0x35, 0x6f, 0xd0, 0x76, 0xe7, 0x1d, 0x9d, 0xc7, 0xa0, 0x55, 0x1, 0x25, 0x51, 0x9e, 0xd5, 0xf0, 0x81, 0x4c, 0x91, 0xd7, 0x33, 0x37, 0x3b}}
return a, nil
}
@ -90,8 +93,8 @@ func templatesJson() (*asset, error) {
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
@ -101,6 +104,12 @@ func Asset(name string) ([]byte, error) {
return nil, fmt.Errorf("Asset %s not found", name)
}
// AssetString returns the asset contents as a string (instead of a []byte).
func AssetString(name string) (string, error) {
data, err := Asset(name)
return string(data), err
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
@ -112,12 +121,18 @@ func MustAsset(name string) []byte {
return a
}
// MustAssetString is like AssetString but panics when Asset would return an
// error. It simplifies safe initialization of global variables.
func MustAssetString(name string) string {
return string(MustAsset(name))
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
@ -127,6 +142,33 @@ func AssetInfo(name string) (os.FileInfo, error) {
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetDigest returns the digest of the file with the given name. It returns an
// error if the asset could not be found or the digest could not be loaded.
func AssetDigest(name string) ([sha256.Size]byte, error) {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
}
return a.digest, nil
}
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
}
// Digests returns a map of all known files and their checksums.
func Digests() (map[string][sha256.Size]byte, error) {
mp := make(map[string][sha256.Size]byte, len(_bindata))
for name := range _bindata {
a, err := _bindata[name]()
if err != nil {
return nil, err
}
mp[name] = a.digest
}
return mp, nil
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
@ -141,6 +183,9 @@ var _bindata = map[string]func() (*asset, error){
"templates.json": templatesJson,
}
// AssetDebug is true if the assets were built with the debug flag enabled.
const AssetDebug = false
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
@ -150,15 +195,15 @@ var _bindata = map[string]func() (*asset, error){
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// then AssetDir("data") would return []string{"foo.txt", "img"},
// AssetDir("data/img") would return []string{"a.png", "b.png"},
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
canonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(canonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
@ -180,11 +225,12 @@ type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"templates.json": &bintree{templatesJson, map[string]*bintree{}},
"templates.json": {templatesJson, map[string]*bintree{}},
}}
// RestoreAsset restores an asset under the given directory
// RestoreAsset restores an asset under the given directory.
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
@ -202,14 +248,10 @@ func RestoreAsset(dir, name string) error {
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
}
// RestoreAssets restores an asset under the given directory recursively
// RestoreAssets restores an asset under the given directory recursively.
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
@ -227,7 +269,6 @@ func RestoreAssets(dir, name string) error {
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
canonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
}

View file

@ -1,16 +1,20 @@
// Code generated by go-bindata. DO NOT EDIT.
// sources:
// postgres_files/000001_init.down.sql
// postgres_files/000001_init.up.sql
// postgres_files/000002_system_settings_table.down.sql
// postgres_files/000002_system_settings_table.up.sql
// postgres_files/000003_blocks_rootid.down.sql
// postgres_files/000003_blocks_rootid.up.sql
// postgres_files/000001_init.down.sql (19B)
// postgres_files/000001_init.up.sql (268B)
// postgres_files/000002_system_settings_table.down.sql (28B)
// postgres_files/000002_system_settings_table.up.sql (97B)
// postgres_files/000003_blocks_rootid.down.sql (40B)
// postgres_files/000003_blocks_rootid.up.sql (51B)
// postgres_files/000004_auth_table.down.sql (39B)
// postgres_files/000004_auth_table.up.sql (491B)
package postgres
import (
"bytes"
"compress/gzip"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
@ -23,7 +27,7 @@ import (
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
return nil, fmt.Errorf("read %q: %w", name, err)
}
var buf bytes.Buffer
@ -31,7 +35,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
return nil, fmt.Errorf("read %q: %w", name, err)
}
if clErr != nil {
return nil, err
@ -41,8 +45,9 @@ func bindataRead(data []byte, name string) ([]byte, error) {
}
type asset struct {
bytes []byte
info os.FileInfo
bytes []byte
info os.FileInfo
digest [sha256.Size]byte
}
type bindataFileInfo struct {
@ -86,8 +91,8 @@ func _000001_initDownSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000001_init.down.sql", size: 19, mode: os.FileMode(420), modTime: time.Unix(1603074564, 0)}
a := &asset{bytes: bytes, info: info}
info := bindataFileInfo{name: "000001_init.down.sql", size: 19, mode: os.FileMode(0644), modTime: time.Unix(1602958996, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe4, 0x9a, 0x67, 0x49, 0xbf, 0xf7, 0xaa, 0x0, 0xe2, 0x48, 0xe4, 0xe2, 0xdc, 0x1, 0x8b, 0xa3, 0x24, 0x4, 0xcc, 0xbd, 0x3d, 0xbd, 0xf8, 0xec, 0xcf, 0x54, 0x9e, 0xc6, 0x83, 0x69, 0x7c, 0xb0}}
return a, nil
}
@ -106,8 +111,8 @@ func _000001_initUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000001_init.up.sql", size: 268, mode: os.FileMode(420), modTime: time.Unix(1607029670, 0)}
a := &asset{bytes: bytes, info: info}
info := bindataFileInfo{name: "000001_init.up.sql", size: 268, mode: os.FileMode(0644), modTime: time.Unix(1603214842, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1d, 0xaf, 0xd8, 0x41, 0x91, 0x97, 0x98, 0xd, 0x84, 0x1f, 0x4, 0xf8, 0xc8, 0x17, 0xa3, 0x20, 0x3f, 0x5b, 0x65, 0xee, 0x26, 0xae, 0x17, 0x8, 0xe7, 0xe, 0x25, 0xca, 0x87, 0xaa, 0xd4, 0xd4}}
return a, nil
}
@ -126,8 +131,8 @@ func _000002_system_settings_tableDownSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000002_system_settings_table.down.sql", size: 28, mode: os.FileMode(420), modTime: time.Unix(1603229117, 0)}
a := &asset{bytes: bytes, info: info}
info := bindataFileInfo{name: "000002_system_settings_table.down.sql", size: 28, mode: os.FileMode(0644), modTime: time.Unix(1603112131, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe4, 0xfd, 0xe6, 0x5d, 0xd1, 0xb2, 0xe9, 0x49, 0x14, 0x3b, 0xec, 0xb, 0x5f, 0x9d, 0x1d, 0x56, 0x13, 0x70, 0x76, 0x78, 0x7e, 0xd7, 0xd2, 0x57, 0x1e, 0xe7, 0x11, 0xb, 0xf9, 0xfb, 0x67, 0xb9}}
return a, nil
}
@ -146,8 +151,8 @@ func _000002_system_settings_tableUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000002_system_settings_table.up.sql", size: 97, mode: os.FileMode(420), modTime: time.Unix(1603229117, 0)}
a := &asset{bytes: bytes, info: info}
info := bindataFileInfo{name: "000002_system_settings_table.up.sql", size: 97, mode: os.FileMode(0644), modTime: time.Unix(1603112131, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x5, 0xac, 0x61, 0xab, 0xf5, 0x91, 0x2f, 0xfd, 0xa8, 0x27, 0xa4, 0x63, 0xd1, 0x2d, 0xf4, 0x79, 0x2a, 0x9d, 0x78, 0x94, 0xb7, 0x7a, 0xaf, 0xcb, 0x9d, 0xae, 0x10, 0x89, 0xb3, 0xeb, 0x1e, 0x5d}}
return a, nil
}
@ -166,8 +171,8 @@ func _000003_blocks_rootidDownSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000003_blocks_rootid.down.sql", size: 40, mode: os.FileMode(420), modTime: time.Unix(1607029815, 0)}
a := &asset{bytes: bytes, info: info}
info := bindataFileInfo{name: "000003_blocks_rootid.down.sql", size: 40, mode: os.FileMode(0644), modTime: time.Unix(1607094225, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1a, 0xb4, 0xdd, 0x99, 0x6e, 0xb8, 0x1a, 0xfd, 0x54, 0x43, 0x8f, 0x9, 0x3a, 0xd1, 0xe4, 0x32, 0x20, 0x3b, 0x43, 0xf4, 0x61, 0x17, 0x9b, 0xff, 0x85, 0xb0, 0x9a, 0x48, 0x9f, 0xfd, 0xbb, 0xcc}}
return a, nil
}
@ -186,8 +191,48 @@ func _000003_blocks_rootidUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000003_blocks_rootid.up.sql", size: 51, mode: os.FileMode(420), modTime: time.Unix(1607029769, 0)}
a := &asset{bytes: bytes, info: info}
info := bindataFileInfo{name: "000003_blocks_rootid.up.sql", size: 51, mode: os.FileMode(0644), modTime: time.Unix(1607094225, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x20, 0x3e, 0xe3, 0x25, 0xb9, 0x60, 0x1a, 0xc2, 0xbf, 0x51, 0x2e, 0xf0, 0xbb, 0x9e, 0x61, 0xb9, 0x16, 0x19, 0xc0, 0x6, 0x7a, 0x81, 0x1c, 0xce, 0xa0, 0xea, 0x91, 0xb2, 0xf, 0xb9, 0xba, 0x91}}
return a, nil
}
var __000004_auth_tableDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\x2d\x4e\x2d\x2a\xb6\xe6\x42\x12\x29\x4e\x2d\x2e\xce\xcc\xcf\x2b\xb6\xe6\x02\x04\x00\x00\xff\xff\xa5\xe0\x77\xaa\x27\x00\x00\x00")
func _000004_auth_tableDownSqlBytes() ([]byte, error) {
return bindataRead(
__000004_auth_tableDownSql,
"000004_auth_table.down.sql",
)
}
func _000004_auth_tableDownSql() (*asset, error) {
bytes, err := _000004_auth_tableDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "000004_auth_table.down.sql", size: 39, mode: os.FileMode(0644), modTime: time.Unix(1606923754, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb6, 0x5b, 0x33, 0x33, 0xd4, 0x68, 0x4, 0xb4, 0xfd, 0x61, 0x61, 0x33, 0x1b, 0xc6, 0x44, 0x5e, 0x57, 0xd0, 0xe7, 0x21, 0x36, 0xff, 0x10, 0x1a, 0xc2, 0xa2, 0xe7, 0x3a, 0xbd, 0x2e, 0xba, 0x66}}
return a, nil
}
var __000004_auth_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xa4\x90\x4f\x4b\x03\x31\x10\xc5\xcf\x9b\x4f\x31\xc7\x5d\xe8\xa1\x16\x7a\xf2\x94\x96\xa8\xf1\xcf\x56\xb2\x41\xec\x69\x19\x36\x23\x06\xbb\x7f\xc8\xa4\xfa\xf5\x25\x08\x16\x9a\xd5\x4b\xe7\xf8\x7b\xc3\xbc\x37\x6f\x6b\x94\xb4\x0a\xac\xdc\x3c\x2a\xd0\x37\x50\xef\x2c\xa8\x57\xdd\xd8\x06\x8e\x4c\x81\xa1\x14\x85\x77\xf0\x22\xcd\xf6\x4e\x9a\xf2\x6a\xb9\xac\x16\xa2\x48\xd2\x80\x3d\x9d\x73\xea\xd1\x1f\x7e\xe1\x6a\xbd\x4e\x70\x42\xe6\xaf\x31\x64\x47\xfa\x37\x6c\x99\xba\x40\xf1\x5c\xc1\x63\x7c\x6f\x99\xc2\xa7\xef\x4e\x16\xab\x93\xe4\x30\x62\xe6\x12\xc6\x89\xe1\x67\xee\x9b\x5d\xbd\x10\x45\x17\x08\x23\xb5\x18\x13\xdb\xe8\x5b\x5d\xdb\x94\x7d\x72\x33\xd4\xd1\x81\x72\xfa\x6c\xf4\x93\x34\x7b\x78\x50\x7b\x28\xbd\xab\x44\x75\x2d\xc4\x3f\x95\x31\x31\xfb\x71\xf8\xa3\xb5\x38\x7e\xd0\x30\x57\x65\x9b\xef\x5e\xf8\xce\x5c\xf0\xef\x00\x00\x00\xff\xff\x16\x8f\x58\x39\xeb\x01\x00\x00")
func _000004_auth_tableUpSqlBytes() ([]byte, error) {
return bindataRead(
__000004_auth_tableUpSql,
"000004_auth_table.up.sql",
)
}
func _000004_auth_tableUpSql() (*asset, error) {
bytes, err := _000004_auth_tableUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "000004_auth_table.up.sql", size: 491, mode: os.FileMode(0644), modTime: time.Unix(1610375570, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x51, 0xbe, 0x84, 0x23, 0xcb, 0x3b, 0x88, 0xbf, 0x9e, 0xd, 0x32, 0x46, 0x47, 0xe1, 0x6e, 0xb2, 0x2e, 0x36, 0x6b, 0xde, 0x38, 0x7f, 0x0, 0x27, 0xd8, 0x10, 0x7e, 0xf2, 0xa4, 0x6, 0x73, 0xd}}
return a, nil
}
@ -195,8 +240,8 @@ func _000003_blocks_rootidUpSql() (*asset, error) {
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
@ -206,6 +251,12 @@ func Asset(name string) ([]byte, error) {
return nil, fmt.Errorf("Asset %s not found", name)
}
// AssetString returns the asset contents as a string (instead of a []byte).
func AssetString(name string) (string, error) {
data, err := Asset(name)
return string(data), err
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
@ -217,12 +268,18 @@ func MustAsset(name string) []byte {
return a
}
// MustAssetString is like AssetString but panics when Asset would return an
// error. It simplifies safe initialization of global variables.
func MustAssetString(name string) string {
return string(MustAsset(name))
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
@ -232,6 +289,33 @@ func AssetInfo(name string) (os.FileInfo, error) {
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetDigest returns the digest of the file with the given name. It returns an
// error if the asset could not be found or the digest could not be loaded.
func AssetDigest(name string) ([sha256.Size]byte, error) {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
}
return a.digest, nil
}
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
}
// Digests returns a map of all known files and their checksums.
func Digests() (map[string][sha256.Size]byte, error) {
mp := make(map[string][sha256.Size]byte, len(_bindata))
for name := range _bindata {
a, err := _bindata[name]()
if err != nil {
return nil, err
}
mp[name] = a.digest
}
return mp, nil
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
@ -243,14 +327,19 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"000001_init.down.sql": _000001_initDownSql,
"000001_init.up.sql": _000001_initUpSql,
"000001_init.down.sql": _000001_initDownSql,
"000001_init.up.sql": _000001_initUpSql,
"000002_system_settings_table.down.sql": _000002_system_settings_tableDownSql,
"000002_system_settings_table.up.sql": _000002_system_settings_tableUpSql,
"000003_blocks_rootid.down.sql": _000003_blocks_rootidDownSql,
"000003_blocks_rootid.up.sql": _000003_blocks_rootidUpSql,
"000002_system_settings_table.up.sql": _000002_system_settings_tableUpSql,
"000003_blocks_rootid.down.sql": _000003_blocks_rootidDownSql,
"000003_blocks_rootid.up.sql": _000003_blocks_rootidUpSql,
"000004_auth_table.down.sql": _000004_auth_tableDownSql,
"000004_auth_table.up.sql": _000004_auth_tableUpSql,
}
// AssetDebug is true if the assets were built with the debug flag enabled.
const AssetDebug = false
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
@ -260,15 +349,15 @@ var _bindata = map[string]func() (*asset, error){
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// then AssetDir("data") would return []string{"foo.txt", "img"},
// AssetDir("data/img") would return []string{"a.png", "b.png"},
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
canonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(canonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
@ -290,16 +379,19 @@ type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"000001_init.down.sql": &bintree{_000001_initDownSql, map[string]*bintree{}},
"000001_init.up.sql": &bintree{_000001_initUpSql, map[string]*bintree{}},
"000002_system_settings_table.down.sql": &bintree{_000002_system_settings_tableDownSql, map[string]*bintree{}},
"000002_system_settings_table.up.sql": &bintree{_000002_system_settings_tableUpSql, map[string]*bintree{}},
"000003_blocks_rootid.down.sql": &bintree{_000003_blocks_rootidDownSql, map[string]*bintree{}},
"000003_blocks_rootid.up.sql": &bintree{_000003_blocks_rootidUpSql, map[string]*bintree{}},
"000001_init.down.sql": {_000001_initDownSql, map[string]*bintree{}},
"000001_init.up.sql": {_000001_initUpSql, map[string]*bintree{}},
"000002_system_settings_table.down.sql": {_000002_system_settings_tableDownSql, map[string]*bintree{}},
"000002_system_settings_table.up.sql": {_000002_system_settings_tableUpSql, map[string]*bintree{}},
"000003_blocks_rootid.down.sql": {_000003_blocks_rootidDownSql, map[string]*bintree{}},
"000003_blocks_rootid.up.sql": {_000003_blocks_rootidUpSql, map[string]*bintree{}},
"000004_auth_table.down.sql": {_000004_auth_tableDownSql, map[string]*bintree{}},
"000004_auth_table.up.sql": {_000004_auth_tableUpSql, map[string]*bintree{}},
}}
// RestoreAsset restores an asset under the given directory
// RestoreAsset restores an asset under the given directory.
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
@ -317,14 +409,10 @@ func RestoreAsset(dir, name string) error {
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
}
// RestoreAssets restores an asset under the given directory recursively
// RestoreAssets restores an asset under the given directory recursively.
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
@ -342,7 +430,6 @@ func RestoreAssets(dir, name string) error {
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
canonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
}

View file

@ -0,0 +1,2 @@
DROP TABLE users;
DROP TABLE sessions;

View file

@ -0,0 +1,24 @@
CREATE TABLE IF NOT EXISTS users (
id VARCHAR(100),
username VARCHAR(100),
email VARCHAR(255),
password VARCHAR(100),
mfa_secret VARCHAR(100),
auth_service VARCHAR(20),
auth_data VARCHAR(255),
props JSON,
create_at BIGINT,
update_at BIGINT,
delete_at BIGINT,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS sessions (
id VARCHAR(100),
token VARCHAR(100),
user_id VARCHAR(100),
props JSON,
create_at BIGINT,
update_at BIGINT,
PRIMARY KEY (id)
);

View file

@ -1,16 +1,20 @@
// Code generated by go-bindata. DO NOT EDIT.
// sources:
// sqlite_files/000001_init.down.sql
// sqlite_files/000001_init.up.sql
// sqlite_files/000002_system_settings_table.down.sql
// sqlite_files/000002_system_settings_table.up.sql
// sqlite_files/000003_blocks_rootid.down.sql
// sqlite_files/000003_blocks_rootid.up.sql
// sqlite_files/000001_init.down.sql (19B)
// sqlite_files/000001_init.up.sql (297B)
// sqlite_files/000002_system_settings_table.down.sql (28B)
// sqlite_files/000002_system_settings_table.up.sql (96B)
// sqlite_files/000003_blocks_rootid.down.sql (40B)
// sqlite_files/000003_blocks_rootid.up.sql (51B)
// sqlite_files/000004_auth_table.down.sql (39B)
// sqlite_files/000004_auth_table.up.sql (491B)
package sqlite
import (
"bytes"
"compress/gzip"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
@ -23,7 +27,7 @@ import (
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
return nil, fmt.Errorf("read %q: %w", name, err)
}
var buf bytes.Buffer
@ -31,7 +35,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
return nil, fmt.Errorf("read %q: %w", name, err)
}
if clErr != nil {
return nil, err
@ -41,8 +45,9 @@ func bindataRead(data []byte, name string) ([]byte, error) {
}
type asset struct {
bytes []byte
info os.FileInfo
bytes []byte
info os.FileInfo
digest [sha256.Size]byte
}
type bindataFileInfo struct {
@ -86,8 +91,8 @@ func _000001_initDownSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000001_init.down.sql", size: 19, mode: os.FileMode(420), modTime: time.Unix(1603074564, 0)}
a := &asset{bytes: bytes, info: info}
info := bindataFileInfo{name: "000001_init.down.sql", size: 19, mode: os.FileMode(0644), modTime: time.Unix(1602958996, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe4, 0x9a, 0x67, 0x49, 0xbf, 0xf7, 0xaa, 0x0, 0xe2, 0x48, 0xe4, 0xe2, 0xdc, 0x1, 0x8b, 0xa3, 0x24, 0x4, 0xcc, 0xbd, 0x3d, 0xbd, 0xf8, 0xec, 0xcf, 0x54, 0x9e, 0xc6, 0x83, 0x69, 0x7c, 0xb0}}
return a, nil
}
@ -106,8 +111,8 @@ func _000001_initUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000001_init.up.sql", size: 297, mode: os.FileMode(420), modTime: time.Unix(1607029839, 0)}
a := &asset{bytes: bytes, info: info}
info := bindataFileInfo{name: "000001_init.up.sql", size: 297, mode: os.FileMode(0644), modTime: time.Unix(1603029845, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xcc, 0xb4, 0xcf, 0xde, 0xfb, 0xcc, 0xa1, 0xf8, 0x73, 0x51, 0x3b, 0xbf, 0x6a, 0xb0, 0xd0, 0x6b, 0xdf, 0xb0, 0x76, 0xaf, 0x4e, 0x89, 0x21, 0xc7, 0x8e, 0xe8, 0x18, 0x50, 0xc7, 0x9, 0xd8, 0xbf}}
return a, nil
}
@ -126,8 +131,8 @@ func _000002_system_settings_tableDownSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000002_system_settings_table.down.sql", size: 28, mode: os.FileMode(420), modTime: time.Unix(1603229117, 0)}
a := &asset{bytes: bytes, info: info}
info := bindataFileInfo{name: "000002_system_settings_table.down.sql", size: 28, mode: os.FileMode(0644), modTime: time.Unix(1603112131, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe4, 0xfd, 0xe6, 0x5d, 0xd1, 0xb2, 0xe9, 0x49, 0x14, 0x3b, 0xec, 0xb, 0x5f, 0x9d, 0x1d, 0x56, 0x13, 0x70, 0x76, 0x78, 0x7e, 0xd7, 0xd2, 0x57, 0x1e, 0xe7, 0x11, 0xb, 0xf9, 0xfb, 0x67, 0xb9}}
return a, nil
}
@ -146,8 +151,8 @@ func _000002_system_settings_tableUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000002_system_settings_table.up.sql", size: 96, mode: os.FileMode(420), modTime: time.Unix(1603229117, 0)}
a := &asset{bytes: bytes, info: info}
info := bindataFileInfo{name: "000002_system_settings_table.up.sql", size: 96, mode: os.FileMode(0644), modTime: time.Unix(1603112131, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x24, 0x6f, 0x66, 0x74, 0xbf, 0xec, 0xae, 0x5a, 0x56, 0x95, 0x4b, 0x4d, 0xaa, 0xf6, 0x8, 0xae, 0x88, 0x62, 0x21, 0x4f, 0xdc, 0xb1, 0x3, 0x30, 0x79, 0xd4, 0xc2, 0x17, 0x2, 0x9e, 0x1c, 0x8e}}
return a, nil
}
@ -166,8 +171,8 @@ func _000003_blocks_rootidDownSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000003_blocks_rootid.down.sql", size: 40, mode: os.FileMode(420), modTime: time.Unix(1607029815, 0)}
a := &asset{bytes: bytes, info: info}
info := bindataFileInfo{name: "000003_blocks_rootid.down.sql", size: 40, mode: os.FileMode(0644), modTime: time.Unix(1607094225, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1a, 0xb4, 0xdd, 0x99, 0x6e, 0xb8, 0x1a, 0xfd, 0x54, 0x43, 0x8f, 0x9, 0x3a, 0xd1, 0xe4, 0x32, 0x20, 0x3b, 0x43, 0xf4, 0x61, 0x17, 0x9b, 0xff, 0x85, 0xb0, 0x9a, 0x48, 0x9f, 0xfd, 0xbb, 0xcc}}
return a, nil
}
@ -186,8 +191,48 @@ func _000003_blocks_rootidUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000003_blocks_rootid.up.sql", size: 51, mode: os.FileMode(420), modTime: time.Unix(1607029769, 0)}
a := &asset{bytes: bytes, info: info}
info := bindataFileInfo{name: "000003_blocks_rootid.up.sql", size: 51, mode: os.FileMode(0644), modTime: time.Unix(1607094225, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x20, 0x3e, 0xe3, 0x25, 0xb9, 0x60, 0x1a, 0xc2, 0xbf, 0x51, 0x2e, 0xf0, 0xbb, 0x9e, 0x61, 0xb9, 0x16, 0x19, 0xc0, 0x6, 0x7a, 0x81, 0x1c, 0xce, 0xa0, 0xea, 0x91, 0xb2, 0xf, 0xb9, 0xba, 0x91}}
return a, nil
}
var __000004_auth_tableDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\x2d\x4e\x2d\x2a\xb6\xe6\x42\x12\x29\x4e\x2d\x2e\xce\xcc\xcf\x2b\xb6\xe6\x02\x04\x00\x00\xff\xff\xa5\xe0\x77\xaa\x27\x00\x00\x00")
func _000004_auth_tableDownSqlBytes() ([]byte, error) {
return bindataRead(
__000004_auth_tableDownSql,
"000004_auth_table.down.sql",
)
}
func _000004_auth_tableDownSql() (*asset, error) {
bytes, err := _000004_auth_tableDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "000004_auth_table.down.sql", size: 39, mode: os.FileMode(0644), modTime: time.Unix(1606923777, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb6, 0x5b, 0x33, 0x33, 0xd4, 0x68, 0x4, 0xb4, 0xfd, 0x61, 0x61, 0x33, 0x1b, 0xc6, 0x44, 0x5e, 0x57, 0xd0, 0xe7, 0x21, 0x36, 0xff, 0x10, 0x1a, 0xc2, 0xa2, 0xe7, 0x3a, 0xbd, 0x2e, 0xba, 0x66}}
return a, nil
}
var __000004_auth_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xa4\xd0\xc1\x4a\x03\x31\x10\x06\xe0\xf3\xe6\x29\xe6\xb8\x0b\x3d\xd4\x42\x4f\x9e\xd2\x12\x35\xa8\x55\xd2\x20\xed\x69\x19\x36\x23\x06\xbb\x9b\x25\x93\xd5\xd7\x97\x20\x58\x68\x56\x2f\xe6\xf8\xfd\x21\x93\xf9\xb7\x46\x49\xab\xc0\xca\xcd\x83\x02\x7d\x03\xbb\x27\x0b\xea\xa0\xf7\x76\x0f\x13\x53\x64\xa8\x45\xe5\x1d\xbc\x48\xb3\xbd\x93\xa6\xbe\x5a\x2e\x9b\x85\xa8\x72\x34\x60\x4f\x97\x4e\x3d\xfa\xd3\x0f\xae\xd6\xeb\x8c\x23\x32\x7f\x86\x58\x3c\xd2\xbf\x62\xcb\xd4\x45\x4a\x97\x09\x4e\xe9\xad\x65\x8a\x1f\xbe\x3b\x8f\x58\x9d\x23\x87\x09\x8b\x29\x31\x8c\x0c\xdf\xc7\xaa\x83\x5d\x88\xaa\x8b\x84\x89\x5a\x4c\xd9\x36\xfa\x56\xef\xb2\x4e\xa3\x9b\x51\x47\x27\x2a\xf5\xd9\xe8\x47\x69\x8e\x70\xaf\x8e\x50\x7b\xd7\x88\xe6\x5a\x88\x3f\x2a\x63\x62\xf6\x61\xf8\xa5\xb5\x14\xde\x69\x98\xab\xb2\x2d\xef\xfe\x73\x9d\xb9\x8f\x7f\x05\x00\x00\xff\xff\x82\xd7\x37\x19\xeb\x01\x00\x00")
func _000004_auth_tableUpSqlBytes() ([]byte, error) {
return bindataRead(
__000004_auth_tableUpSql,
"000004_auth_table.up.sql",
)
}
func _000004_auth_tableUpSql() (*asset, error) {
bytes, err := _000004_auth_tableUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "000004_auth_table.up.sql", size: 491, mode: os.FileMode(0644), modTime: time.Unix(1610375590, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb6, 0xf9, 0x52, 0xb2, 0x63, 0x7b, 0x65, 0xea, 0x26, 0xeb, 0xa3, 0x8c, 0x5, 0x70, 0xd7, 0x4e, 0xd0, 0x63, 0x75, 0xc6, 0x7d, 0xf4, 0x5d, 0x11, 0x62, 0x8e, 0x94, 0x43, 0x25, 0x0, 0xaa, 0xf2}}
return a, nil
}
@ -195,8 +240,8 @@ func _000003_blocks_rootidUpSql() (*asset, error) {
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
@ -206,6 +251,12 @@ func Asset(name string) ([]byte, error) {
return nil, fmt.Errorf("Asset %s not found", name)
}
// AssetString returns the asset contents as a string (instead of a []byte).
func AssetString(name string) (string, error) {
data, err := Asset(name)
return string(data), err
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
@ -217,12 +268,18 @@ func MustAsset(name string) []byte {
return a
}
// MustAssetString is like AssetString but panics when Asset would return an
// error. It simplifies safe initialization of global variables.
func MustAssetString(name string) string {
return string(MustAsset(name))
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
@ -232,6 +289,33 @@ func AssetInfo(name string) (os.FileInfo, error) {
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetDigest returns the digest of the file with the given name. It returns an
// error if the asset could not be found or the digest could not be loaded.
func AssetDigest(name string) ([sha256.Size]byte, error) {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
}
return a.digest, nil
}
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
}
// Digests returns a map of all known files and their checksums.
func Digests() (map[string][sha256.Size]byte, error) {
mp := make(map[string][sha256.Size]byte, len(_bindata))
for name := range _bindata {
a, err := _bindata[name]()
if err != nil {
return nil, err
}
mp[name] = a.digest
}
return mp, nil
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
@ -243,14 +327,19 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"000001_init.down.sql": _000001_initDownSql,
"000001_init.up.sql": _000001_initUpSql,
"000001_init.down.sql": _000001_initDownSql,
"000001_init.up.sql": _000001_initUpSql,
"000002_system_settings_table.down.sql": _000002_system_settings_tableDownSql,
"000002_system_settings_table.up.sql": _000002_system_settings_tableUpSql,
"000003_blocks_rootid.down.sql": _000003_blocks_rootidDownSql,
"000003_blocks_rootid.up.sql": _000003_blocks_rootidUpSql,
"000002_system_settings_table.up.sql": _000002_system_settings_tableUpSql,
"000003_blocks_rootid.down.sql": _000003_blocks_rootidDownSql,
"000003_blocks_rootid.up.sql": _000003_blocks_rootidUpSql,
"000004_auth_table.down.sql": _000004_auth_tableDownSql,
"000004_auth_table.up.sql": _000004_auth_tableUpSql,
}
// AssetDebug is true if the assets were built with the debug flag enabled.
const AssetDebug = false
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
@ -260,15 +349,15 @@ var _bindata = map[string]func() (*asset, error){
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// then AssetDir("data") would return []string{"foo.txt", "img"},
// AssetDir("data/img") would return []string{"a.png", "b.png"},
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
canonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(canonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
@ -290,16 +379,19 @@ type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"000001_init.down.sql": &bintree{_000001_initDownSql, map[string]*bintree{}},
"000001_init.up.sql": &bintree{_000001_initUpSql, map[string]*bintree{}},
"000002_system_settings_table.down.sql": &bintree{_000002_system_settings_tableDownSql, map[string]*bintree{}},
"000002_system_settings_table.up.sql": &bintree{_000002_system_settings_tableUpSql, map[string]*bintree{}},
"000003_blocks_rootid.down.sql": &bintree{_000003_blocks_rootidDownSql, map[string]*bintree{}},
"000003_blocks_rootid.up.sql": &bintree{_000003_blocks_rootidUpSql, map[string]*bintree{}},
"000001_init.down.sql": {_000001_initDownSql, map[string]*bintree{}},
"000001_init.up.sql": {_000001_initUpSql, map[string]*bintree{}},
"000002_system_settings_table.down.sql": {_000002_system_settings_tableDownSql, map[string]*bintree{}},
"000002_system_settings_table.up.sql": {_000002_system_settings_tableUpSql, map[string]*bintree{}},
"000003_blocks_rootid.down.sql": {_000003_blocks_rootidDownSql, map[string]*bintree{}},
"000003_blocks_rootid.up.sql": {_000003_blocks_rootidUpSql, map[string]*bintree{}},
"000004_auth_table.down.sql": {_000004_auth_tableDownSql, map[string]*bintree{}},
"000004_auth_table.up.sql": {_000004_auth_tableUpSql, map[string]*bintree{}},
}}
// RestoreAsset restores an asset under the given directory
// RestoreAsset restores an asset under the given directory.
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
@ -317,14 +409,10 @@ func RestoreAsset(dir, name string) error {
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
}
// RestoreAssets restores an asset under the given directory recursively
// RestoreAssets restores an asset under the given directory recursively.
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
@ -342,7 +430,6 @@ func RestoreAssets(dir, name string) error {
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
canonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
}

View file

@ -0,0 +1,2 @@
DROP TABLE users;
DROP TABLE sessions;

View file

@ -0,0 +1,24 @@
CREATE TABLE IF NOT EXISTS users (
id VARCHAR(100),
username VARCHAR(100),
email VARCHAR(255),
password VARCHAR(100),
mfa_secret VARCHAR(100),
auth_service VARCHAR(20),
auth_data VARCHAR(255),
props TEXT,
create_at BIGINT,
update_at BIGINT,
delete_at BIGINT,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS sessions (
id VARCHAR(100),
token VARCHAR(100),
user_id VARCHAR(100),
props TEXT,
create_at BIGINT,
update_at BIGINT,
PRIMARY KEY (id)
);

View file

@ -0,0 +1,91 @@
package sqlstore
import (
"encoding/json"
"time"
sq "github.com/Masterminds/squirrel"
"github.com/mattermost/mattermost-octo-tasks/server/model"
)
func (s *SQLStore) GetSession(token string, expireTime int64) (*model.Session, error) {
query := s.getQueryBuilder().
Select("id", "token", "user_id", "props").
From("sessions").
Where(sq.Eq{"token": token}).
Where(sq.Gt{"update_at": time.Now().Unix() - expireTime})
row := query.QueryRow()
session := model.Session{}
var propsBytes []byte
err := row.Scan(&session.ID, &session.Token, &session.UserID, &propsBytes)
if err != nil {
return nil, err
}
err = json.Unmarshal(propsBytes, &session.Props)
if err != nil {
return nil, err
}
return &session, nil
}
func (s *SQLStore) CreateSession(session *model.Session) error {
now := time.Now().Unix()
propsBytes, err := json.Marshal(session.Props)
if err != nil {
return err
}
query := s.getQueryBuilder().Insert("sessions").
Columns("id", "token", "user_id", "props", "create_at", "update_at").
Values(session.ID, session.Token, session.UserID, propsBytes, now, now)
_, err = query.Exec()
return err
}
func (s *SQLStore) RefreshSession(session *model.Session) error {
now := time.Now().Unix()
query := s.getQueryBuilder().Update("sessions").
Set("update_at", now)
_, err := query.Exec()
return err
}
func (s *SQLStore) UpdateSession(session *model.Session) error {
now := time.Now().Unix()
propsBytes, err := json.Marshal(session.Props)
if err != nil {
return err
}
query := s.getQueryBuilder().Update("sessions").
Set("update_at", now).
Set("props", propsBytes)
_, err = query.Exec()
return err
}
func (s *SQLStore) DeleteSession(sessionId string) error {
query := s.getQueryBuilder().Delete("sessions").
Where("id", sessionId)
_, err := query.Exec()
return err
}
func (s *SQLStore) CleanUpSessions(expireTime int64) error {
query := s.getQueryBuilder().Delete("sessions").
Where(sq.Lt{"update_at": time.Now().Unix() - expireTime})
_, err := query.Exec()
return err
}

View file

@ -0,0 +1,79 @@
package sqlstore
import (
"encoding/json"
"time"
"github.com/mattermost/mattermost-octo-tasks/server/model"
sq "github.com/Masterminds/squirrel"
)
func (s *SQLStore) getUserByCondition(condition sq.Eq) (*model.User, error) {
query := s.getQueryBuilder().
Select("id", "username", "email", "password", "mfa_secret", "auth_service", "auth_data", "props", "create_at", "update_at", "delete_at").
From("users").
Where(sq.Eq{"delete_at": 0}).
Where(condition)
row := query.QueryRow()
user := model.User{}
var propsBytes []byte
err := row.Scan(&user.ID, &user.Username, &user.Email, &user.Password, &user.MfaSecret, &user.AuthService, &user.AuthData, &propsBytes, &user.CreateAt, &user.UpdateAt, &user.DeleteAt)
if err != nil {
return nil, err
}
err = json.Unmarshal(propsBytes, &user.Props)
if err != nil {
return nil, err
}
return &user, nil
}
func (s *SQLStore) GetUserById(userID string) (*model.User, error) {
return s.getUserByCondition(sq.Eq{"id": userID})
}
func (s *SQLStore) GetUserByEmail(email string) (*model.User, error) {
return s.getUserByCondition(sq.Eq{"email": email})
}
func (s *SQLStore) GetUserByUsername(username string) (*model.User, error) {
return s.getUserByCondition(sq.Eq{"username": username})
}
func (s *SQLStore) CreateUser(user *model.User) error {
now := time.Now().Unix()
propsBytes, err := json.Marshal(user.Props)
if err != nil {
return err
}
query := s.getQueryBuilder().Insert("users").
Columns("id", "username", "email", "password", "mfa_secret", "auth_service", "auth_data", "props", "create_at", "update_at", "delete_at").
Values(user.ID, user.Username, user.Email, user.Password, user.MfaSecret, user.AuthService, user.AuthData, propsBytes, now, now, 0)
_, err = query.Exec()
return err
}
func (s *SQLStore) UpdateUser(user *model.User) error {
now := time.Now().Unix()
propsBytes, err := json.Marshal(user.Props)
if err != nil {
return err
}
query := s.getQueryBuilder().Update("users").
Set("username", user.Username).
Set("email", user.Email).
Set("props", propsBytes).
Set("update_at", now)
_, err = query.Exec()
return err
}

View file

@ -17,4 +17,15 @@ type Store interface {
Shutdown() error
GetSystemSettings() (map[string]string, error)
SetSystemSetting(key string, value string) error
GetUserById(userID string) (*model.User, error)
GetUserByEmail(email string) (*model.User, error)
GetUserByUsername(username string) (*model.User, error)
CreateUser(user *model.User) error
UpdateUser(user *model.User) error
GetSession(token string, expireTime int64) (*model.Session, error)
CreateSession(session *model.Session) error
RefreshSession(session *model.Session) error
UpdateSession(session *model.Session) error
DeleteSession(sessionId string) error
CleanUpSessions(expireTime int64) error
}

View file

@ -11,7 +11,7 @@
"check": "eslint --ext .tsx,.ts . --quiet --cache",
"fix": "eslint --ext .tsx,.ts . --quiet --fix --cache",
"i18n-extract": "formatjs extract src/**/*.tsx src/**/*.ts --out-file i18n/tmp.json; formatjs compile i18n/tmp.json --out-file i18n/en.json; rm i18n/tmp.json",
"runserver-test": "cd cypress && ../../bin/octoserver",
"runserver-test": "cd cypress && ../../bin/octoserver --single-user",
"cypress:ci": "start-server-and-test runserver-test http://localhost:8088 cypress:run",
"cypress:run": "cypress run",
"cypress:run:chrome": "cypress run --browser chrome",

View file

@ -1,50 +1,85 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useState} from 'react'
import React from 'react'
import {IntlProvider} from 'react-intl'
import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
} from 'react-router-dom'
import client from './octoClient'
import {IUser, UserContext} from './user'
import {getCurrentLanguage, getMessages, storeLanguage} from './i18n'
import {FlashMessages} from './components/flashMessages'
import LoginPage from './pages/loginPage'
import RegisterPage from './pages/registerPage'
import BoardPage from './pages/boardPage'
export default function App(): JSX.Element {
const [language, setLanguage] = useState(getCurrentLanguage())
const setAndStoreLanguage = (lang: string) => {
storeLanguage(lang)
setLanguage(lang)
}
return (
<IntlProvider
locale={language}
messages={getMessages(language)}
>
<FlashMessages milliseconds={2000}/>
<Router>
<div id='frame'>
<div id='main'>
<Switch>
<Route path='/login'>
<LoginPage/>
</Route>
<Route path='/'>
<BoardPage setLanguage={setAndStoreLanguage}/>
</Route>
<Route path='/board'>
<BoardPage setLanguage={setAndStoreLanguage}/>
</Route>
</Switch>
</div>
</div>
</Router>
</IntlProvider>
)
type State = {
language: string,
user: IUser|null,
initialLoad: boolean,
}
export default class App extends React.PureComponent<unknown, State> {
constructor(props: unknown) {
super(props)
this.state = {
language: getCurrentLanguage(),
user: null,
initialLoad: false,
}
}
public componentDidMount(): void {
client.getMe().then((user: IUser|null) => {
this.setState({user, initialLoad: true})
})
}
setAndStoreLanguage = (lang: string): void => {
storeLanguage(lang)
this.setState({language: lang})
}
public render(): JSX.Element {
return (
<IntlProvider
locale={this.state.language}
messages={getMessages(this.state.language)}
>
<UserContext.Provider value={this.state.user}>
<FlashMessages milliseconds={2000}/>
<Router>
<div id='frame'>
<div id='main'>
<Switch>
<Route path='/login'>
<LoginPage/>
</Route>
<Route path='/register'>
<RegisterPage/>
</Route>
<Route path='/'>
{this.state.initialLoad && !this.state.user && <Redirect to='login'/>}
<BoardPage setLanguage={this.setAndStoreLanguage}/>
</Route>
<Route path='/board'>
{this.state.initialLoad && !this.state.user && <Redirect to='login'/>}
<BoardPage setLanguage={this.setAndStoreLanguage}/>
</Route>
</Switch>
</div>
</div>
</Router>
</UserContext.Provider>
</IntlProvider>
)
}
}

View file

@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {IBlock, IMutableBlock} from './blocks/block'
import {IUser} from './user'
import {Utils} from './utils'
//
@ -8,15 +9,66 @@ import {Utils} from './utils'
//
class OctoClient {
serverUrl: string
token?: string
constructor(serverUrl?: string) {
constructor(serverUrl?: string, token?: string) {
this.serverUrl = serverUrl || window.location.origin
this.token = token
Utils.log(`OctoClient serverUrl: ${this.serverUrl}`)
}
async login(username: string, password: string): Promise<boolean> {
const path = '/api/v1/login'
const body = JSON.stringify({username, password, type: 'normal'})
const response = await fetch(this.serverUrl + path, {
method: 'POST',
headers: this.headers(),
body,
})
if (response.status === 200) {
const responseJson = (await response.json() || {}) as {token?: string}
this.token = responseJson.token
if (responseJson.token !== '') {
localStorage.setItem('sessionId', this.token || '')
return true
}
return false
}
return false
}
async register(email: string, username: string, password: string): Promise<boolean> {
const path = '/api/v1/register'
const body = JSON.stringify({email, username, password})
const response = await fetch(this.serverUrl + path, {
method: 'POST',
headers: this.headers(),
body,
})
if (response.status === 200) {
return true
}
return false
}
private headers() {
return {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: this.token ? 'Bearer ' + this.token : '',
}
}
async getMe(): Promise<IUser|null> {
const path = '/api/v1/users/me'
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
const user = (await response.json()) as IUser || null
return user
}
async getSubtree(rootId?: string, levels = 2): Promise<IBlock[]> {
const path = `/api/v1/blocks/${rootId}/subtree?l=${levels}`
const response = await fetch(this.serverUrl + path)
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
const blocks = (await response.json() || []) as IMutableBlock[]
this.fixBlocks(blocks)
return blocks
@ -24,7 +76,7 @@ class OctoClient {
async exportFullArchive(): Promise<IBlock[]> {
const path = '/api/v1/blocks/export'
const response = await fetch(this.serverUrl + path)
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
const blocks = (await response.json() || []) as IMutableBlock[]
this.fixBlocks(blocks)
return blocks
@ -38,10 +90,7 @@ class OctoClient {
const body = JSON.stringify(blocks)
return fetch(this.serverUrl + '/api/v1/blocks/import', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
headers: this.headers(),
body,
})
}
@ -62,7 +111,7 @@ class OctoClient {
}
private async getBlocksWithPath(path: string): Promise<IBlock[]> {
const response = await fetch(this.serverUrl + path)
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
const blocks = (await response.json() || []) as IMutableBlock[]
this.fixBlocks(blocks)
return blocks
@ -98,10 +147,7 @@ class OctoClient {
Utils.log(`deleteBlock: ${blockId}`)
return fetch(this.serverUrl + `/api/v1/blocks/${encodeURIComponent(blockId)}`, {
method: 'DELETE',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
headers: this.headers(),
})
}
@ -117,10 +163,7 @@ class OctoClient {
const body = JSON.stringify(blocks)
return fetch(this.serverUrl + '/api/v1/blocks', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
headers: this.headers(),
body,
})
}
@ -138,6 +181,7 @@ class OctoClient {
// TIPTIP: Leave out Content-Type here, it will be automatically set by the browser
headers: {
Accept: 'application/json',
Authorization: this.token ? 'Bearer ' + this.token : '',
},
body: formData,
})
@ -161,6 +205,6 @@ class OctoClient {
}
}
const client = new OctoClient()
const client = new OctoClient(undefined, localStorage.getItem('sessionId') || '')
export default client

View file

@ -2,26 +2,34 @@
// See LICENSE.txt for license information.
import React from 'react'
import {Utils} from '../utils'
import {
withRouter,
RouteComponentProps,
Link,
} from 'react-router-dom'
import Button from '../widgets/buttons/button'
import client from '../octoClient'
import './loginPage.scss'
type Props = {
}
type Props = RouteComponentProps
type State = {
username: string
password: string
}
export default class LoginPage extends React.PureComponent<Props, State> {
class LoginPage extends React.PureComponent<Props, State> {
state = {
username: '',
password: '',
}
private handleLogin = (): void => {
Utils.log('Logging in')
private handleLogin = async (): Promise<void> => {
const logged = await client.login(this.state.username, this.state.password)
if (logged) {
this.props.history.push('/')
}
}
render(): React.ReactNode {
@ -45,7 +53,10 @@ export default class LoginPage extends React.PureComponent<Props, State> {
/>
</div>
<Button onClick={this.handleLogin}>{'Login'}</Button>
<Link to='/register'>{'or create an account if you don\'t have one'}</Link>
</div>
)
}
}
export default withRouter(LoginPage)

View file

@ -0,0 +1,27 @@
.RegisterPage {
border: 1px solid #cccccc;
border-radius: 15px;
width: 450px;
height: 400px;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
.email, .username, .password {
margin-bottom: 5px;
label {
display: inline-block;
width: 140px;
}
input {
display: inline-block;
width: 250px;
border: 1px solid #cccccc;
border-radius: 4px;
}
}
.Button {
margin-top: 10px;
}
}

View file

@ -0,0 +1,74 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'
import {
withRouter,
RouteComponentProps,
Link,
} from 'react-router-dom'
import Button from '../widgets/buttons/button'
import client from '../octoClient'
import './registerPage.scss'
type Props = RouteComponentProps
type State = {
email: string
username: string
password: string
}
class RegisterPage extends React.PureComponent<Props, State> {
state = {
email: '',
username: '',
password: '',
}
private handleRegister = async (): Promise<void> => {
const registered = await client.register(this.state.email, this.state.username, this.state.password)
if (registered) {
const logged = await client.login(this.state.username, this.state.password)
if (logged) {
this.props.history.push('/')
}
}
}
render(): React.ReactNode {
return (
<div className='RegisterPage'>
<div className='email'>
<label htmlFor='login-email'>{'Email'}</label>
<input
id='login-email'
value={this.state.email}
onChange={(e) => this.setState({email: e.target.value})}
/>
</div>
<div className='username'>
<label htmlFor='login-username'>{'Username'}</label>
<input
id='login-username'
value={this.state.username}
onChange={(e) => this.setState({username: e.target.value})}
/>
</div>
<div className='password'>
<label htmlFor='login-username'>{'Password'}</label>
<input
id='login-password'
type='password'
value={this.state.password}
onChange={(e) => this.setState({password: e.target.value})}
/>
</div>
<Button onClick={this.handleRegister}>{'Register'}</Button>
<Link to='/login'>{'or login if you already have an account'}</Link>
</div>
)
}
}
export default withRouter(RegisterPage)

17
webapp/src/user.tsx Normal file
View file

@ -0,0 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'
const UserContext = React.createContext(null as IUser|null)
interface IUser {
id: string,
username: string,
email: string,
props: Record<string, any>,
createAt: number,
updateAt: number,
}
export {IUser, UserContext}

View file

@ -12,7 +12,7 @@ import (
func runOctoTasks(ctx context.Context) *exec.Cmd {
// cmd := exec.CommandContext(ctx, "bin/octoserver.exe", "--monitorpid", strconv.FormatInt(int64(os.Getpid()), 10))
cmd := exec.CommandContext(ctx, "bin/octoserver.exe")
cmd := exec.CommandContext(ctx, "bin/octoserver.exe --single-user")
// cmd := exec.CommandContext(ctx, "cmd.exe", "/C", "start", "./bin/octoserver.exe", "--monitorpid", strconv.FormatInt(int64(os.Getpid()), 10))
// cmd := exec.CommandContext(ctx, "cmd.exe", "/C", "start", "./bin/octoserver.exe")