mirror of
https://github.com/mattermost/focalboard.git
synced 2025-01-26 18:48:15 +02:00
Merge pull request #52 from mattermost/single-user-token
Single user token
This commit is contained in:
commit
ac142e4a98
96
.vscode/launch.json
vendored
96
.vscode/launch.json
vendored
@ -11,20 +11,36 @@
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/server/main",
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Go: Test Current File",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "test",
|
||||
"remotePath": "",
|
||||
"port": 8888,
|
||||
"host": "127.0.0.1",
|
||||
"program": "${file}",
|
||||
"env": {},
|
||||
"args": [],
|
||||
"showLog": true
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Go: Launch Windows App",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/win",
|
||||
"cwd": "${workspaceFolder}/win/temp",
|
||||
},
|
||||
{
|
||||
"name": "Go: Launch Linux App",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/linux",
|
||||
"cwd": "${workspaceFolder}/linux/dist/focalboard-app",
|
||||
},
|
||||
{
|
||||
"name": "Go: Test Current File",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "test",
|
||||
"remotePath": "",
|
||||
"port": 8888,
|
||||
"host": "127.0.0.1",
|
||||
"program": "${file}",
|
||||
"env": {},
|
||||
"args": [],
|
||||
"showLog": true
|
||||
},
|
||||
{
|
||||
"name": "Attach by Process ID",
|
||||
"processId": "${command:PickProcess}",
|
||||
@ -34,30 +50,30 @@
|
||||
],
|
||||
"type": "pwa-node"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Jest: run all tests",
|
||||
"program": "${workspaceRoot}/webapp/node_modules/jest/bin/jest.js",
|
||||
"cwd": "${workspaceRoot}/webapp",
|
||||
"args": [
|
||||
"--verbose",
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Jest: run current file",
|
||||
"program": "${workspaceRoot}/webapp/node_modules/jest/bin/jest.js",
|
||||
"cwd": "${workspaceRoot}/webapp",
|
||||
"args": [
|
||||
"${fileBasename}",
|
||||
"--verbose",
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
},
|
||||
]
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Jest: run all tests",
|
||||
"program": "${workspaceRoot}/webapp/node_modules/jest/bin/jest.js",
|
||||
"cwd": "${workspaceRoot}/webapp",
|
||||
"args": [
|
||||
"--verbose",
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Jest: run current file",
|
||||
"program": "${workspaceRoot}/webapp/node_modules/jest/bin/jest.js",
|
||||
"cwd": "${workspaceRoot}/webapp",
|
||||
"args": [
|
||||
"${fileBasename}",
|
||||
"--verbose",
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
24
Makefile
24
Makefile
@ -56,24 +56,6 @@ server-linux-package: server-linux webapp
|
||||
cd package && tar -czvf ../dist/focalboard-server-linux-amd64.tar.gz ${PACKAGE_FOLDER}
|
||||
rm -rf package
|
||||
|
||||
server-single-user:
|
||||
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=dev")
|
||||
cd server; go build -ldflags '$(LDFLAGS)' -o ../bin/focalboard-server ./main --single-user
|
||||
|
||||
server-mac-single-user:
|
||||
mkdir -p bin/mac
|
||||
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=mac")
|
||||
cd server; env GOOS=darwin GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -o ../bin/mac/focalboard-server ./main --single-user
|
||||
|
||||
server-linux-single-user:
|
||||
mkdir -p bin/linux
|
||||
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=linux")
|
||||
cd server; env GOOS=linux GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -o ../bin/linux/focalboard-server ./main --single-user
|
||||
|
||||
server-win-single-user:
|
||||
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=win")
|
||||
cd server; env GOOS=windows GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -o ../bin/focalboard-server.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
|
||||
@ -81,9 +63,9 @@ generate:
|
||||
|
||||
server-lint:
|
||||
@if ! [ -x "$$(command -v golangci-lint)" ]; then \
|
||||
echo "golangci-lint is not installed. Please see https://github.com/golangci/golangci-lint#install for installation instructions."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "golangci-lint is not installed. Please see https://github.com/golangci/golangci-lint#install for installation instructions."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
cd server; golangci-lint run -p format -p unused -p complexity -p bugs -p performance -E asciicheck -E depguard -E dogsled -E dupl -E funlen -E gochecknoglobals -E gochecknoinits -E goconst -E gocritic -E godot -E godox -E goerr113 -E goheader -E golint -E gomnd -E gomodguard -E goprintffuncname -E gosimple -E interfacer -E lll -E misspell -E nlreturn -E nolintlint -E stylecheck -E unconvert -E whitespace -E wsl --skip-dirs services/store/sqlstore/migrations/ ./...
|
||||
|
||||
server-test:
|
||||
|
@ -6,6 +6,6 @@
|
||||
"useSSL": false,
|
||||
"webpath": "./pack",
|
||||
"filespath": "./files",
|
||||
"telemetry": true,
|
||||
"localOnly": true
|
||||
"telemetry": true,
|
||||
"localOnly": true
|
||||
}
|
||||
|
16
config.json
16
config.json
@ -8,12 +8,12 @@
|
||||
"useSSL": false,
|
||||
"webpath": "./webapp/pack",
|
||||
"filespath": "./files",
|
||||
"telemetry": true,
|
||||
"webhook_update": [],
|
||||
"secret": "this-is-a-secret-string",
|
||||
"session_expire_time": 2592000,
|
||||
"session_refresh_time": 18000,
|
||||
"localOnly": false,
|
||||
"enableLocalMode": true,
|
||||
"localModeSocketLocation": "/var/tmp/focalboard_local.socket"
|
||||
"telemetry": true,
|
||||
"webhook_update": [],
|
||||
"secret": "this-is-a-secret-string",
|
||||
"session_expire_time": 2592000,
|
||||
"session_refresh_time": 18000,
|
||||
"localOnly": false,
|
||||
"enableLocalMode": true,
|
||||
"localModeSocketLocation": "/var/tmp/focalboard_local.socket"
|
||||
}
|
||||
|
@ -2,4 +2,7 @@ module github.com/mattermost/focalboard/linux
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/webview/webview v0.0.0-20200724072439-e0c01595b361
|
||||
require (
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/webview/webview v0.0.0-20200724072439-e0c01595b361
|
||||
)
|
||||
|
@ -1,2 +1,4 @@
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/webview/webview v0.0.0-20200724072439-e0c01595b361 h1:e0+/fQY5l9NdCwPsEg9S8AgE5lFhZ/6UX+b2KkpIBFg=
|
||||
github.com/webview/webview v0.0.0-20200724072439-e0c01595b361/go.mod h1:rpXAuuHgyEJb6kXcXldlkOjU6y4x+YcASKKXJNUhh0Y=
|
||||
|
@ -2,19 +2,25 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/webview/webview"
|
||||
)
|
||||
|
||||
var sessionToken string = "su-" + uuid.New().String()
|
||||
|
||||
func runServer(ctx context.Context) {
|
||||
cmd := exec.CommandContext(ctx, "./focalboard-server", "--monitorpid", strconv.FormatInt(int64(os.Getpid()), 10), "--single-user")
|
||||
cmd := exec.CommandContext(ctx, "./focalboard-server", "--monitorpid", strconv.FormatInt(int64(os.Getpid()), 10), "-single-user")
|
||||
cmd.Env = []string{fmt.Sprintf("FOCALBOARD_SINGLE_USER_TOKEN=%s", sessionToken)}
|
||||
cmd.Stdout = os.Stdout
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Println("Failed to start server")
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Just ran subprocess %d, exiting\n", cmd.Process.Pid)
|
||||
@ -29,6 +35,10 @@ func main() {
|
||||
|
||||
w.SetTitle("Focalboard")
|
||||
w.SetSize(1024, 768, webview.HintNone)
|
||||
|
||||
script := fmt.Sprintf("localStorage.setItem('sessionId', '%s');", sessionToken)
|
||||
w.Init(script)
|
||||
|
||||
w.Navigate("http://localhost:8088")
|
||||
w.Run()
|
||||
cancel()
|
||||
|
@ -9,6 +9,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
private var serverProcess: Process?
|
||||
var serverPort = 8088
|
||||
var sessionToken: String = ""
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
copyResources()
|
||||
@ -68,7 +69,23 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private func generateSessionToken() -> String {
|
||||
let bytesCount = 16
|
||||
var randomNumber = ""
|
||||
var randomBytes = [UInt8](repeating: 0, count: bytesCount)
|
||||
|
||||
let status = SecRandomCopyBytes(kSecRandomDefault, bytesCount, &randomBytes)
|
||||
if status != errSecSuccess {
|
||||
fatalError("SecRandomCopyBytes ERROR: \(status)")
|
||||
}
|
||||
randomNumber = randomBytes.map({String(format: "%02hhx", $0)}).joined(separator: "")
|
||||
|
||||
return "su-" + randomNumber
|
||||
}
|
||||
|
||||
private func startServer() {
|
||||
sessionToken = generateSessionToken()
|
||||
|
||||
let cwdUrl = webFolder()
|
||||
let executablePath = Bundle.main.path(forResource: "resources/bin/focalboard-server", ofType: "")
|
||||
|
||||
@ -76,7 +93,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
NSLog("pid: \(pid)")
|
||||
let serverProcess = Process()
|
||||
serverProcess.currentDirectoryPath = cwdUrl.path
|
||||
serverProcess.arguments = ["-monitorpid", "\(pid)", "-port", "\(serverPort)", "--single-user"]
|
||||
serverProcess.arguments = ["-monitorpid", "\(pid)", "-port", "\(serverPort)", "-single-user"]
|
||||
serverProcess.environment = ["FOCALBOARD_SINGLE_USER_TOKEN": sessionToken]
|
||||
serverProcess.launchPath = executablePath
|
||||
serverProcess.launch()
|
||||
self.serverProcess = serverProcess
|
||||
|
@ -17,9 +17,9 @@ class ViewController:
|
||||
|
||||
webView.navigationDelegate = self
|
||||
webView.uiDelegate = self
|
||||
webView.isHidden = true
|
||||
|
||||
clearWebViewCache()
|
||||
loadHomepage()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(onServerStarted), name: AppDelegate.serverStartedNotification, object: nil)
|
||||
@ -40,10 +40,22 @@ class ViewController:
|
||||
@objc func onServerStarted() {
|
||||
NSLog("onServerStarted")
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
self.updateSessionToken()
|
||||
self.loadHomepage()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateSessionToken() {
|
||||
let appDelegate = NSApplication.shared.delegate as! AppDelegate
|
||||
let script = WKUserScript(
|
||||
source: "localStorage.setItem('sessionId', '\(appDelegate.sessionToken)');",
|
||||
injectionTime: .atDocumentStart,
|
||||
forMainFrameOnly: true
|
||||
)
|
||||
webView.configuration.userContentController.removeAllUserScripts()
|
||||
webView.configuration.userContentController.addUserScript(script)
|
||||
}
|
||||
|
||||
private func loadHomepage() {
|
||||
let appDelegate = NSApplication.shared.delegate as! AppDelegate
|
||||
let port = appDelegate.serverPort
|
||||
@ -143,8 +155,14 @@ class ViewController:
|
||||
NSLog("webView didFinish navigation: \(webView.url?.absoluteString ?? "")")
|
||||
// Disable right-click menu
|
||||
webView.evaluateJavaScript("document.body.setAttribute('oncontextmenu', 'event.preventDefault();');", completionHandler: nil)
|
||||
webView.isHidden = false
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
|
||||
webView.isHidden = false
|
||||
}
|
||||
|
||||
|
||||
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
|
||||
if let frame = navigationAction.targetFrame,
|
||||
frame.isMainFrame {
|
||||
|
@ -27,12 +27,15 @@ const (
|
||||
// REST APIs
|
||||
|
||||
type API struct {
|
||||
appBuilder func() *app.App
|
||||
singleUser bool
|
||||
appBuilder func() *app.App
|
||||
singleUserToken string
|
||||
}
|
||||
|
||||
func NewAPI(appBuilder func() *app.App, singleUser bool) *API {
|
||||
return &API{appBuilder: appBuilder, singleUser: singleUser}
|
||||
func NewAPI(appBuilder func() *app.App, singleUserToken string) *API {
|
||||
return &API{
|
||||
appBuilder: appBuilder,
|
||||
singleUserToken: singleUserToken,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *API) app() *app.App {
|
||||
|
@ -78,6 +78,12 @@ func isValidPassword(password string) error {
|
||||
}
|
||||
|
||||
func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if len(a.singleUserToken) > 0 {
|
||||
// Not permitted in single-user mode
|
||||
errorResponse(w, http.StatusUnauthorized, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
requestBody, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, nil, err)
|
||||
@ -111,6 +117,12 @@ func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
if len(a.singleUserToken) > 0 {
|
||||
// Not permitted in single-user mode
|
||||
errorResponse(w, http.StatusUnauthorized, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
requestBody, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, nil, err)
|
||||
@ -164,6 +176,12 @@ func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (a *API) handleChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
if len(a.singleUserToken) > 0 {
|
||||
// Not permitted in single-user mode
|
||||
errorResponse(w, http.StatusUnauthorized, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
userID := vars["userID"]
|
||||
|
||||
@ -198,12 +216,19 @@ func (a *API) sessionRequired(handler func(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request), required bool) 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 {
|
||||
token, _ := auth.ParseAuthTokenFromRequest(r)
|
||||
|
||||
log.Printf(`Single User: %v`, len(a.singleUserToken) > 0)
|
||||
if len(a.singleUserToken) > 0 {
|
||||
if required && (token != a.singleUserToken) {
|
||||
errorResponse(w, http.StatusUnauthorized, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
session := &model.Session{
|
||||
ID: "single-user",
|
||||
Token: "single-user",
|
||||
Token: token,
|
||||
UserID: "single-user",
|
||||
CreateAt: now,
|
||||
UpdateAt: now,
|
||||
@ -213,11 +238,10 @@ func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
token, _ := auth.ParseAuthTokenFromRequest(r)
|
||||
session, err := a.app().GetSession(token)
|
||||
if err != nil {
|
||||
if required {
|
||||
errorResponse(w, http.StatusUnauthorized, map[string]string{"error": err.Error()}, err)
|
||||
errorResponse(w, http.StatusUnauthorized, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,8 @@ func TestGetParentID(t *testing.T) {
|
||||
cfg := config.Configuration{}
|
||||
store := mockstore.NewMockStore(ctrl)
|
||||
auth := auth.New(&cfg, store)
|
||||
wsserver := ws.NewServer(auth, true)
|
||||
sessionToken := "TESTTOKEN"
|
||||
wsserver := ws.NewServer(auth, sessionToken)
|
||||
webhook := webhook.NewClient(&cfg)
|
||||
app := New(&cfg, store, auth, wsserver, &mocks.FileBackend{}, webhook)
|
||||
|
||||
|
@ -63,10 +63,11 @@ type Client struct {
|
||||
HttpHeader map[string]string
|
||||
}
|
||||
|
||||
func NewClient(url string) *Client {
|
||||
func NewClient(url string, sessionToken string) *Client {
|
||||
url = strings.TrimRight(url, "/")
|
||||
headers := map[string]string{
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"Authorization": "Bearer " + sessionToken,
|
||||
}
|
||||
return &Client{url, url + API_URL_SUFFIX, &http.Client{}, headers}
|
||||
}
|
||||
|
@ -27,13 +27,14 @@ func getTestConfig() *config.Configuration {
|
||||
}
|
||||
|
||||
func SetupTestHelper() *TestHelper {
|
||||
sessionToken := "TESTTOKEN"
|
||||
th := &TestHelper{}
|
||||
srv, err := server.New(getTestConfig(), true)
|
||||
srv, err := server.New(getTestConfig(), sessionToken)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
th.Server = srv
|
||||
th.Client = client.NewClient(srv.Config().ServerRoot)
|
||||
th.Client = client.NewClient(srv.Config().ServerRoot, sessionToken)
|
||||
|
||||
return th
|
||||
}
|
||||
|
@ -71,6 +71,16 @@ func main() {
|
||||
singleUser = *pSingleUser
|
||||
}
|
||||
|
||||
singleUserToken := ""
|
||||
if singleUser {
|
||||
singleUserToken = os.Getenv("FOCALBOARD_SINGLE_USER_TOKEN")
|
||||
if len(singleUserToken) < 1 {
|
||||
log.Fatal("The FOCALBOARD_SINGLE_USER_TOKEN environment variable must be set for single user mode ")
|
||||
return
|
||||
}
|
||||
log.Printf("Single user mode")
|
||||
}
|
||||
|
||||
if pMonitorPid != nil && *pMonitorPid > 0 {
|
||||
monitorPid(*pMonitorPid)
|
||||
}
|
||||
@ -81,7 +91,7 @@ func main() {
|
||||
config.Port = *pPort
|
||||
}
|
||||
|
||||
server, err := server.New(config, singleUser)
|
||||
server, err := server.New(config, singleUserToken)
|
||||
if err != nil {
|
||||
log.Fatal("server.New ERROR: ", err)
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ type Server struct {
|
||||
localModeServer *http.Server
|
||||
}
|
||||
|
||||
func New(cfg *config.Configuration, singleUser bool) (*Server, error) {
|
||||
func New(cfg *config.Configuration, singleUserToken string) (*Server, error) {
|
||||
logger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -63,7 +63,7 @@ func New(cfg *config.Configuration, singleUser bool) (*Server, error) {
|
||||
|
||||
auth := auth.New(cfg, store)
|
||||
|
||||
wsServer := ws.NewServer(auth, singleUser)
|
||||
wsServer := ws.NewServer(auth, singleUserToken)
|
||||
|
||||
filesBackendSettings := model.FileSettings{}
|
||||
filesBackendSettings.SetDefaults(false)
|
||||
@ -78,7 +78,7 @@ func New(cfg *config.Configuration, singleUser bool) (*Server, error) {
|
||||
webhookClient := webhook.NewClient(cfg)
|
||||
|
||||
appBuilder := func() *app.App { return app.New(cfg, store, auth, wsServer, filesBackend, webhookClient) }
|
||||
api := api.NewAPI(appBuilder, singleUser)
|
||||
api := api.NewAPI(appBuilder, singleUserToken)
|
||||
|
||||
// Local router for admin APIs
|
||||
localRouter := mux.NewRouter()
|
||||
@ -157,7 +157,7 @@ func New(cfg *config.Configuration, singleUser bool) (*Server, error) {
|
||||
"port": cfg.Port == config.DefaultPort,
|
||||
"useSSL": cfg.UseSSL,
|
||||
"dbType": cfg.DBType,
|
||||
"single_user": singleUser,
|
||||
"single_user": len(singleUserToken) > 0,
|
||||
}
|
||||
})
|
||||
telemetryService.RegisterTracker("activity", func() map[string]interface{} {
|
||||
|
@ -18,11 +18,11 @@ type IsValidSessionToken func(token string) bool
|
||||
|
||||
// Server is a WebSocket server.
|
||||
type Server struct {
|
||||
upgrader websocket.Upgrader
|
||||
listeners map[string][]*websocket.Conn
|
||||
mu sync.RWMutex
|
||||
auth *auth.Auth
|
||||
singleUser bool
|
||||
upgrader websocket.Upgrader
|
||||
listeners map[string][]*websocket.Conn
|
||||
mu sync.RWMutex
|
||||
auth *auth.Auth
|
||||
singleUserToken string
|
||||
}
|
||||
|
||||
// UpdateMsg is sent on block updates
|
||||
@ -50,7 +50,7 @@ type websocketSession struct {
|
||||
}
|
||||
|
||||
// NewServer creates a new Server.
|
||||
func NewServer(auth *auth.Auth, singleUser bool) *Server {
|
||||
func NewServer(auth *auth.Auth, singleUserToken string) *Server {
|
||||
return &Server{
|
||||
listeners: make(map[string][]*websocket.Conn),
|
||||
upgrader: websocket.Upgrader{
|
||||
@ -58,8 +58,8 @@ func NewServer(auth *auth.Auth, singleUser bool) *Server {
|
||||
return true
|
||||
},
|
||||
},
|
||||
auth: auth,
|
||||
singleUser: singleUser,
|
||||
auth: auth,
|
||||
singleUserToken: singleUserToken,
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ func (ws *Server) handleWebSocketOnChange(w http.ResponseWriter, r *http.Request
|
||||
|
||||
wsSession := websocketSession{
|
||||
client: client,
|
||||
isAuthenticated: ws.singleUser,
|
||||
isAuthenticated: false,
|
||||
}
|
||||
|
||||
// Simple message handling loop
|
||||
@ -134,8 +134,8 @@ func (ws *Server) handleWebSocketOnChange(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
func (ws *Server) isValidSessionToken(token string) bool {
|
||||
if ws.singleUser {
|
||||
return true
|
||||
if len(ws.singleUserToken) > 0 {
|
||||
return token == ws.singleUserToken
|
||||
}
|
||||
|
||||
session, err := ws.auth.GetSession(token)
|
||||
@ -160,10 +160,6 @@ func (ws *Server) authenticateListener(wsSession *websocketSession, token string
|
||||
}
|
||||
|
||||
func (ws *Server) checkAuthentication(wsSession *websocketSession, command *WebsocketCommand) bool {
|
||||
if ws.singleUser {
|
||||
return true
|
||||
}
|
||||
|
||||
if wsSession.isAuthenticated {
|
||||
return true
|
||||
}
|
||||
|
@ -6,6 +6,6 @@
|
||||
"useSSL": false,
|
||||
"webpath": "../pack",
|
||||
"filespath": "../../files",
|
||||
"telemetry": false,
|
||||
"webhook_update": []
|
||||
"telemetry": false,
|
||||
"webhook_update": []
|
||||
}
|
||||
|
@ -10,6 +10,11 @@ describe('Create and delete board / card', () => {
|
||||
const boardTitle = `Test Board (${timestamp})`;
|
||||
const cardTitle = `Test Card (${timestamp})`;
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.setItem('sessionId', 'TESTTOKEN');
|
||||
cy.expect(localStorage.getItem('sessionId')).to.eq('TESTTOKEN');
|
||||
});
|
||||
|
||||
it('Can create and delete a board and card', () => {
|
||||
cy.visit('/');
|
||||
cy.contains('+ Add Board').click({force: true});
|
||||
|
@ -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/focalboard-server --single-user",
|
||||
"runserver-test": "cd cypress && FOCALBOARD_SINGLE_USER_TOKEN=TESTTOKEN ../../bin/focalboard-server -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",
|
||||
|
@ -11,16 +11,17 @@ import {Utils} from './utils'
|
||||
//
|
||||
class OctoClient {
|
||||
readonly serverUrl: string
|
||||
token?: string
|
||||
readonly readToken?: string
|
||||
get token(): string {
|
||||
return localStorage.getItem('sessionId') || ''
|
||||
}
|
||||
get readToken(): string {
|
||||
const queryString = new URLSearchParams(window.location.search)
|
||||
const readToken = queryString.get('r') || ''
|
||||
return readToken
|
||||
}
|
||||
|
||||
constructor(
|
||||
serverUrl?: string,
|
||||
token?: string,
|
||||
readToken?: string) {
|
||||
constructor(serverUrl?: string) {
|
||||
this.serverUrl = serverUrl || window.location.origin
|
||||
this.token = token
|
||||
this.readToken = readToken
|
||||
Utils.log(`OctoClient serverUrl: ${this.serverUrl}`)
|
||||
}
|
||||
|
||||
@ -46,9 +47,8 @@ class OctoClient {
|
||||
}
|
||||
|
||||
const responseJson = (await this.getJson(response)) as {token?: string}
|
||||
this.token = responseJson.token || ''
|
||||
if (this.token) {
|
||||
localStorage.setItem('sessionId', this.token)
|
||||
if (responseJson.token) {
|
||||
localStorage.setItem('sessionId', responseJson.token)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -330,12 +330,6 @@ class OctoClient {
|
||||
}
|
||||
}
|
||||
|
||||
function getReadToken(): string {
|
||||
const queryString = new URLSearchParams(window.location.search)
|
||||
const readToken = queryString.get('r') || ''
|
||||
return readToken
|
||||
}
|
||||
|
||||
const client = new OctoClient(undefined, localStorage.getItem('sessionId') || '', getReadToken())
|
||||
const client = new OctoClient()
|
||||
|
||||
export default client
|
||||
|
@ -4,5 +4,6 @@ go 1.15
|
||||
|
||||
require (
|
||||
github.com/gonutz/w32 v1.0.0
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/zserge/lorca v0.1.10-0.20200301195127-a3e43396a47e
|
||||
)
|
||||
|
@ -1,5 +1,7 @@
|
||||
github.com/gonutz/w32 v1.0.0 h1:3t1z6ZfkFvirjFYBx9pHeHBuKoN/VBVk9yHb/m2Ll/k=
|
||||
github.com/gonutz/w32 v1.0.0/go.mod h1:Rc/YP5K9gv0FW4p6X9qL3E7Y56lfMflEol1fLElfMW4=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/zserge/lorca v0.1.9 h1:vbDdkqdp2/rmeg8GlyCewY2X8Z+b0s7BqWyIQL/gakc=
|
||||
github.com/zserge/lorca v0.1.9/go.mod h1:bVmnIbIRlOcoV285KIRSe4bUABKi7R7384Ycuum6e4A=
|
||||
github.com/zserge/lorca v0.1.10-0.20200301195127-a3e43396a47e h1:RqKGfaG8v1WBC6JX5vhG7GocwY1lENlMiraQibyGRsY=
|
||||
|
44
win/main.go
44
win/main.go
@ -2,26 +2,32 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/gonutz/w32"
|
||||
"github.com/google/uuid"
|
||||
"github.com/zserge/lorca"
|
||||
)
|
||||
|
||||
var sessionToken string = "su-" + uuid.New().String()
|
||||
|
||||
func runServer(ctx context.Context) *exec.Cmd {
|
||||
// cmd := exec.CommandContext(ctx, "focalboard-server.exe", "--monitorpid", strconv.FormatInt(int64(os.Getpid()), 10), "--single-user")
|
||||
cmd := exec.CommandContext(ctx, "focalboard-server.exe", "--single-user")
|
||||
// cmd := exec.CommandContext(ctx, "focalboard-server.exe", "--monitorpid", strconv.FormatInt(int64(os.Getpid()), 10), "-single-user")
|
||||
cmd := exec.CommandContext(ctx, "focalboard-server.exe", "-single-user")
|
||||
// cmd := exec.CommandContext(ctx, "cmd.exe", "/C", "start", "./bin/focalboard-server.exe", "--monitorpid", strconv.FormatInt(int64(os.Getpid()), 10))
|
||||
// cmd := exec.CommandContext(ctx, "cmd.exe", "/C", "start", "./bin/focalboard-server.exe")
|
||||
|
||||
// cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
cmd.Env = []string{fmt.Sprintf("FOCALBOARD_SINGLE_USER_TOKEN=%s", sessionToken)}
|
||||
cmd.Stdout = os.Stdout
|
||||
go func() {
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Println("Failed to start server")
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Just ran subprocess %d, exiting\n", cmd.Process.Pid)
|
||||
@ -49,20 +55,38 @@ func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cmd := runServer(ctx)
|
||||
|
||||
ui, err := lorca.New("http://localhost:8088", "", 1024, 768)
|
||||
defer func() {
|
||||
log.Println("Cleanup")
|
||||
cancel()
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
log.Fatal("failed to kill server process: ", err)
|
||||
}
|
||||
|
||||
if r := recover(); r != nil {
|
||||
log.Fatal("ERROR: ", r)
|
||||
}
|
||||
}()
|
||||
|
||||
ui, err := lorca.New("", "", 1024, 768)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Panic(err)
|
||||
}
|
||||
defer ui.Close()
|
||||
|
||||
if err := ui.Load("http://localhost:8088"); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
script := fmt.Sprintf("localStorage.setItem('sessionId', '%s');", sessionToken)
|
||||
value := ui.Eval(script)
|
||||
if err := value.Err(); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
// defer ui.Close()
|
||||
|
||||
log.Printf("Started")
|
||||
<-ui.Done()
|
||||
|
||||
log.Printf("App Closed")
|
||||
cancel()
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
log.Fatal("failed to kill process: ", err)
|
||||
}
|
||||
log.Println("App Closed")
|
||||
}
|
||||
|
||||
func hideConsole() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user