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:
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

View file

@ -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{

View file

@ -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")
}

View file

@ -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)

View file

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

View file

@ -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
}

View file

@ -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

View file

@ -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 {
<Route path='/login'>
<LoginPage/>
</Route>
<Route path='/register'>
<RegisterPage/>
</Route>
<Route path='/'>
{!client.token && <Redirect to='/login'/>}
<BoardPage setLanguage={setAndStoreLanguage}/>
</Route>
<Route path='/board'>
{!client.token && <Redirect to='/login'/>}
<BoardPage setLanguage={setAndStoreLanguage}/>
</Route>
</Switch>

View file

@ -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<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[]> {
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<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 +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<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 +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

View file

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

View file

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

View file

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