1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-02-13 19:42:12 +02:00

Preliminary auth implementation

This commit is contained in:
Jesús Espino 2020-12-04 16:03:09 +01:00
parent 99cefc5356
commit f491241c1a
12 changed files with 209 additions and 30 deletions

View File

@ -24,6 +24,20 @@ server-linux:
server-win: server-win:
cd server; env GOOS=windows GOARCH=amd64 go build -o ../bin/octoserver.exe ./main 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: generate:
cd server; go get -modfile=go.tools.mod github.com/golang/mock/mockgen 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 cd server; go get -modfile=go.tools.mod github.com/jteeuwen/go-bindata
@ -45,6 +59,9 @@ server-doc:
watch-server: watch-server:
cd server; modd cd server; modd
watch-server-single-user:
cd server; env OCTOSERVER_ARGS=--single-user modd
webapp: webapp:
cd webapp; npm run pack cd webapp; npm run pack

View File

@ -60,12 +60,12 @@ func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) {
} }
if loginData.Type == "normal" { 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 { if err != nil {
errorResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) errorResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return return
} }
json, err := json.Marshal(jwtToken) json, err := json.Marshal(map[string]string{"token": token})
if err != nil { if err != nil {
log.Printf(`ERROR json.Marshal: %v`, r) log.Printf(`ERROR json.Marshal: %v`, r)
errorResponse(w, http.StatusInternalServerError, nil) 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) { 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) { return func(w http.ResponseWriter, r *http.Request) {
log.Printf(`Single User: %v`, a.singleUser)
if a.singleUser { if a.singleUser {
now := time.Now().Unix() now := time.Now().Unix()
session := &model.Session{ session := &model.Session{

View File

@ -1,6 +1,8 @@
package app package app
import ( import (
"log"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/mattermost/mattermost-octo-tasks/server/model" "github.com/mattermost/mattermost-octo-tasks/server/model"
"github.com/mattermost/mattermost-octo-tasks/server/services/auth" "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) { if !auth.ComparePassword(user.Password, password) {
log.Printf("Not valid passowrd. %s (%s)\n", password, user.Password)
return "", errors.New("invalid username or password") return "", errors.New("invalid username or password")
} }

View File

@ -56,11 +56,13 @@ func main() {
// Command line args // Command line args
pMonitorPid := flag.Int("monitorpid", -1, "a process ID") pMonitorPid := flag.Int("monitorpid", -1, "a process ID")
pPort := flag.Int("port", config.Port, "the port number") pPort := flag.Int("port", config.Port, "the port number")
pSingleUser := flag.Bool("single-user", false, "single user mode")
flag.Parse()
singleUser := false singleUser := false
if pSingleUser := flag.Bool("single-user", false, "single user mode"); pSingleUser != nil { if pSingleUser != nil {
singleUser = *pSingleUser singleUser = *pSingleUser
} }
flag.Parse()
if pMonitorPid != nil && *pMonitorPid > 0 { if pMonitorPid != nil && *pMonitorPid > 0 {
monitorPid(*pMonitorPid) monitorPid(*pMonitorPid)

View File

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

View File

@ -39,7 +39,6 @@ func HashPassword(password string) string {
// ComparePassword compares the hash // ComparePassword compares the hash
func ComparePassword(hash string, password string) bool { func ComparePassword(hash string, password string) bool {
if len(password) == 0 || len(hash) == 0 { if len(password) == 0 || len(hash) == 0 {
return false return false
} }

View File

@ -12,7 +12,7 @@ const (
HEADER_TOKEN = "token" HEADER_TOKEN = "token"
HEADER_AUTH = "Authorization" HEADER_AUTH = "Authorization"
HEADER_BEARER = "BEARER" HEADER_BEARER = "BEARER"
SESSION_COOKIE_TOKEN = "MMAUTHTOKEN" SESSION_COOKIE_TOKEN = "OCTOTASKSAUTHTOKEN"
) )
type TokenLocation int type TokenLocation int

View File

@ -7,13 +7,17 @@ import {
BrowserRouter as Router, BrowserRouter as Router,
Switch, Switch,
Route, Route,
Redirect,
} from 'react-router-dom' } from 'react-router-dom'
import client from './octoClient'
import {getCurrentLanguage, getMessages, storeLanguage} from './i18n' import {getCurrentLanguage, getMessages, storeLanguage} from './i18n'
import {FlashMessages} from './components/flashMessages' import {FlashMessages} from './components/flashMessages'
import LoginPage from './pages/loginPage' import LoginPage from './pages/loginPage'
import RegisterPage from './pages/registerPage'
import BoardPage from './pages/boardPage' import BoardPage from './pages/boardPage'
export default function App(): JSX.Element { export default function App(): JSX.Element {
@ -35,10 +39,15 @@ export default function App(): JSX.Element {
<Route path='/login'> <Route path='/login'>
<LoginPage/> <LoginPage/>
</Route> </Route>
<Route path='/register'>
<RegisterPage/>
</Route>
<Route path='/'> <Route path='/'>
{!client.token && <Redirect to='/login'/>}
<BoardPage setLanguage={setAndStoreLanguage}/> <BoardPage setLanguage={setAndStoreLanguage}/>
</Route> </Route>
<Route path='/board'> <Route path='/board'>
{!client.token && <Redirect to='/login'/>}
<BoardPage setLanguage={setAndStoreLanguage}/> <BoardPage setLanguage={setAndStoreLanguage}/>
</Route> </Route>
</Switch> </Switch>

View File

@ -8,15 +8,59 @@ import {Utils} from './utils'
// //
class OctoClient { class OctoClient {
serverUrl: string serverUrl: string
token?: string
constructor(serverUrl?: string) { constructor(serverUrl?: string, token?: string) {
this.serverUrl = serverUrl || window.location.origin this.serverUrl = serverUrl || window.location.origin
this.token = token
Utils.log(`OctoClient serverUrl: ${this.serverUrl}`) 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
}
headers() {
return {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: this.token ? 'Bearer ' + this.token : '',
}
}
async getSubtree(rootId?: string, levels = 2): Promise<IBlock[]> { async getSubtree(rootId?: string, levels = 2): Promise<IBlock[]> {
const path = `/api/v1/blocks/${rootId}/subtree?l=${levels}` 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[] const blocks = (await response.json() || []) as IMutableBlock[]
this.fixBlocks(blocks) this.fixBlocks(blocks)
return blocks return blocks
@ -24,7 +68,7 @@ class OctoClient {
async exportFullArchive(): Promise<IBlock[]> { async exportFullArchive(): Promise<IBlock[]> {
const path = '/api/v1/blocks/export' 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[] const blocks = (await response.json() || []) as IMutableBlock[]
this.fixBlocks(blocks) this.fixBlocks(blocks)
return blocks return blocks
@ -38,10 +82,7 @@ class OctoClient {
const body = JSON.stringify(blocks) const body = JSON.stringify(blocks)
return fetch(this.serverUrl + '/api/v1/blocks/import', { return fetch(this.serverUrl + '/api/v1/blocks/import', {
method: 'POST', method: 'POST',
headers: { headers: this.headers(),
Accept: 'application/json',
'Content-Type': 'application/json',
},
body, body,
}) })
} }
@ -62,7 +103,7 @@ class OctoClient {
} }
private async getBlocksWithPath(path: string): Promise<IBlock[]> { 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[] const blocks = (await response.json() || []) as IMutableBlock[]
this.fixBlocks(blocks) this.fixBlocks(blocks)
return blocks return blocks
@ -98,10 +139,7 @@ class OctoClient {
Utils.log(`deleteBlock: ${blockId}`) Utils.log(`deleteBlock: ${blockId}`)
return fetch(this.serverUrl + `/api/v1/blocks/${encodeURIComponent(blockId)}`, { return fetch(this.serverUrl + `/api/v1/blocks/${encodeURIComponent(blockId)}`, {
method: 'DELETE', method: 'DELETE',
headers: { headers: this.headers(),
Accept: 'application/json',
'Content-Type': 'application/json',
},
}) })
} }
@ -117,10 +155,7 @@ class OctoClient {
const body = JSON.stringify(blocks) const body = JSON.stringify(blocks)
return fetch(this.serverUrl + '/api/v1/blocks', { return fetch(this.serverUrl + '/api/v1/blocks', {
method: 'POST', method: 'POST',
headers: { headers: this.headers(),
Accept: 'application/json',
'Content-Type': 'application/json',
},
body, body,
}) })
} }
@ -138,6 +173,7 @@ class OctoClient {
// TIPTIP: Leave out Content-Type here, it will be automatically set by the browser // TIPTIP: Leave out Content-Type here, it will be automatically set by the browser
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
Authorization: this.token ? 'Bearer ' + this.token : '',
}, },
body: formData, body: formData,
}) })
@ -161,6 +197,6 @@ class OctoClient {
} }
} }
const client = new OctoClient() const client = new OctoClient(undefined, localStorage.getItem('sessionId') || '')
export default client export default client

View File

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

View File

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

View File

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