Merge pull request #24 from mattermost/auth
Initial Auth implementation
This commit is contained in:
commit
3d8a80fb11
38 changed files with 1839 additions and 222 deletions
17
Makefile
17
Makefile
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
138
server/api/auth.go
Normal 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, ®isterData)
|
||||
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
121
server/app/auth.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
24
server/model/user.go
Normal 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"`
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
125
server/services/auth/password.go
Normal file
125
server/services/auth/password.go
Normal 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
|
||||
}
|
165
server/services/auth/password_test.go
Normal file
165
server/services/auth/password_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
67
server/services/auth/request_parser.go
Normal file
67
server/services/auth/request_parser.go
Normal 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
|
||||
}
|
48
server/services/auth/request_parser_test.go
Normal file
48
server/services/auth/request_parser_test.go
Normal 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))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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, "/")...)...)
|
||||
}
|
||||
|
||||
|
|
|
@ -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, "/")...)...)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
DROP TABLE users;
|
||||
DROP TABLE sessions;
|
|
@ -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)
|
||||
);
|
|
@ -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, "/")...)...)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
DROP TABLE users;
|
||||
DROP TABLE sessions;
|
|
@ -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)
|
||||
);
|
91
server/services/store/sqlstore/session.go
Normal file
91
server/services/store/sqlstore/session.go
Normal 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
|
||||
}
|
79
server/services/store/sqlstore/user.go
Normal file
79
server/services/store/sqlstore/user.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
27
webapp/src/pages/registerPage.scss
Normal file
27
webapp/src/pages/registerPage.scss
Normal 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;
|
||||
}
|
||||
}
|
74
webapp/src/pages/registerPage.tsx
Normal file
74
webapp/src/pages/registerPage.tsx
Normal 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
17
webapp/src/user.tsx
Normal 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}
|
|
@ -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")
|
||||
|
||||
|
|
Loading…
Reference in a new issue