diff --git a/Makefile b/Makefile index 59ac52199..094a00085 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,20 @@ server-linux: server-win: cd server; env GOOS=windows GOARCH=amd64 go build -o ../bin/octoserver.exe ./main +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 @@ -45,6 +59,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 diff --git a/server/api/auth.go b/server/api/auth.go index 0bf59c1a1..d214c5ab4 100644 --- a/server/api/auth.go +++ b/server/api/auth.go @@ -60,12 +60,12 @@ func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) { } if loginData.Type == "normal" { - jwtToken, err := a.app().Login(loginData.Username, loginData.Email, loginData.Password, loginData.MfaToken) + 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(jwtToken) + json, err := json.Marshal(map[string]string{"token": token}) if err != nil { log.Printf(`ERROR json.Marshal: %v`, r) errorResponse(w, http.StatusInternalServerError, nil) @@ -111,6 +111,7 @@ func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) { 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{ diff --git a/server/app/auth.go b/server/app/auth.go index 7266b89c4..3bfec91d4 100644 --- a/server/app/auth.go +++ b/server/app/auth.go @@ -1,6 +1,8 @@ package app import ( + "log" + "github.com/google/uuid" "github.com/mattermost/mattermost-octo-tasks/server/model" "github.com/mattermost/mattermost-octo-tasks/server/services/auth" @@ -38,6 +40,7 @@ func (a *App) Login(username string, email string, password string, mfaToken str } if !auth.ComparePassword(user.Password, password) { + log.Printf("Not valid passowrd. %s (%s)\n", password, user.Password) return "", errors.New("invalid username or password") } diff --git a/server/main/main.go b/server/main/main.go index 17fe05848..6f187a81b 100644 --- a/server/main/main.go +++ b/server/main/main.go @@ -56,11 +56,13 @@ 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 := flag.Bool("single-user", false, "single user mode"); pSingleUser != nil { + if pSingleUser != nil { singleUser = *pSingleUser } - flag.Parse() if pMonitorPid != nil && *pMonitorPid > 0 { monitorPid(*pMonitorPid) diff --git a/server/modd.conf b/server/modd.conf index 96e2b7722..7611502b2 100644 --- a/server/modd.conf +++ b/server/modd.conf @@ -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 } diff --git a/server/services/auth/password.go b/server/services/auth/password.go index b9b31ee68..2915d8fe8 100644 --- a/server/services/auth/password.go +++ b/server/services/auth/password.go @@ -39,7 +39,6 @@ func HashPassword(password string) string { // ComparePassword compares the hash func ComparePassword(hash string, password string) bool { - if len(password) == 0 || len(hash) == 0 { return false } diff --git a/server/services/auth/request_parser.go b/server/services/auth/request_parser.go index a0f073da1..161f87624 100644 --- a/server/services/auth/request_parser.go +++ b/server/services/auth/request_parser.go @@ -12,7 +12,7 @@ const ( HEADER_TOKEN = "token" HEADER_AUTH = "Authorization" HEADER_BEARER = "BEARER" - SESSION_COOKIE_TOKEN = "MMAUTHTOKEN" + SESSION_COOKIE_TOKEN = "OCTOTASKSAUTHTOKEN" ) type TokenLocation int diff --git a/webapp/src/app.tsx b/webapp/src/app.tsx index 077bf7bcc..27f4c86cd 100644 --- a/webapp/src/app.tsx +++ b/webapp/src/app.tsx @@ -7,13 +7,17 @@ import { BrowserRouter as Router, Switch, Route, + Redirect, } from 'react-router-dom' +import client from './octoClient' + 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 { @@ -35,10 +39,15 @@ export default function App(): JSX.Element { + + + + {!client.token && } + {!client.token && } diff --git a/webapp/src/octoClient.ts b/webapp/src/octoClient.ts index 0d7c0a628..8819383f1 100644 --- a/webapp/src/octoClient.ts +++ b/webapp/src/octoClient.ts @@ -8,15 +8,59 @@ 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 { + 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 { + 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 + } + + headers() { + return { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: this.token ? 'Bearer ' + this.token : '', + } + } + async getSubtree(rootId?: string, levels = 2): Promise { 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 +68,7 @@ class OctoClient { async exportFullArchive(): Promise { 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 +82,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 +103,7 @@ class OctoClient { } private async getBlocksWithPath(path: string): Promise { - 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 +139,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 +155,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 +173,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 +197,6 @@ class OctoClient { } } -const client = new OctoClient() +const client = new OctoClient(undefined, localStorage.getItem('sessionId') || '') export default client diff --git a/webapp/src/pages/loginPage.tsx b/webapp/src/pages/loginPage.tsx index 97e81554f..a2376e401 100644 --- a/webapp/src/pages/loginPage.tsx +++ b/webapp/src/pages/loginPage.tsx @@ -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 { +class LoginPage extends React.PureComponent { state = { username: '', password: '', } - private handleLogin = (): void => { - Utils.log('Logging in') + private handleLogin = async (): Promise => { + 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 { /> + {'or create an account if you don\'t have one'} ) } } + +export default withRouter(LoginPage) diff --git a/webapp/src/pages/registerPage.scss b/webapp/src/pages/registerPage.scss new file mode 100644 index 000000000..bb013d303 --- /dev/null +++ b/webapp/src/pages/registerPage.scss @@ -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; + } +} diff --git a/webapp/src/pages/registerPage.tsx b/webapp/src/pages/registerPage.tsx new file mode 100644 index 000000000..79adbc8b5 --- /dev/null +++ b/webapp/src/pages/registerPage.tsx @@ -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 { + state = { + email: '', + username: '', + password: '', + } + + private handleRegister = async (): Promise => { + 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 ( +
+
+ + this.setState({email: e.target.value})} + /> +
+
+ + this.setState({username: e.target.value})} + /> +
+
+ + this.setState({password: e.target.value})} + /> +
+ + {'or login if you already have an account'} +
+ ) + } +} +export default withRouter(RegisterPage)