More work on auth
This commit is contained in:
parent
4cc6ecbc64
commit
e5941d6440
6 changed files with 168 additions and 44 deletions
|
@ -10,6 +10,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/mattermost/mattermost-octo-tasks/server/app"
|
"github.com/mattermost/mattermost-octo-tasks/server/app"
|
||||||
|
@ -34,18 +35,21 @@ func (a *API) app() *app.App {
|
||||||
|
|
||||||
func (a *API) RegisterRoutes(r *mux.Router) {
|
func (a *API) RegisterRoutes(r *mux.Router) {
|
||||||
r.HandleFunc("/api/v1/blocks", a.sessionRequired(a.handleGetBlocks)).Methods("GET")
|
r.HandleFunc("/api/v1/blocks", a.sessionRequired(a.handleGetBlocks)).Methods("GET")
|
||||||
r.HandleFunc("/api/v1/blocks", a.handlePostBlocks).Methods("POST")
|
r.HandleFunc("/api/v1/blocks", a.sessionRequired(a.handlePostBlocks)).Methods("POST")
|
||||||
r.HandleFunc("/api/v1/blocks/{blockID}", a.handleDeleteBlock).Methods("DELETE")
|
r.HandleFunc("/api/v1/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE")
|
||||||
r.HandleFunc("/api/v1/blocks/{blockID}/subtree", a.handleGetSubTree).Methods("GET")
|
r.HandleFunc("/api/v1/blocks/{blockID}/subtree", a.sessionRequired(a.handleGetSubTree)).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/login", a.handleLogin).Methods("POST")
|
r.HandleFunc("/api/v1/login", a.handleLogin).Methods("POST")
|
||||||
r.HandleFunc("/api/v1/register", a.handleRegister).Methods("POST")
|
r.HandleFunc("/api/v1/register", a.handleRegister).Methods("POST")
|
||||||
|
|
||||||
r.HandleFunc("/api/v1/files", a.handleUploadFile).Methods("POST")
|
r.HandleFunc("/api/v1/files", a.sessionRequired(a.handleUploadFile)).Methods("POST")
|
||||||
r.HandleFunc("/files/{filename}", a.handleServeFile).Methods("GET")
|
r.HandleFunc("/files/{filename}", a.sessionRequired(a.handleServeFile)).Methods("GET")
|
||||||
|
|
||||||
r.HandleFunc("/api/v1/blocks/export", a.handleExport).Methods("GET")
|
r.HandleFunc("/api/v1/blocks/export", a.sessionRequired(a.handleExport)).Methods("GET")
|
||||||
r.HandleFunc("/api/v1/blocks/import", a.handleImport).Methods("POST")
|
r.HandleFunc("/api/v1/blocks/import", a.sessionRequired(a.handleImport)).Methods("POST")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
|
func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -137,6 +141,65 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
|
||||||
jsonStringResponse(w, http.StatusOK, "{}")
|
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) {
|
func (a *API) handleDeleteBlock(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
blockID := vars["blockID"]
|
blockID := vars["blockID"]
|
||||||
|
|
|
@ -23,6 +23,15 @@ func (a *App) GetSession(token string) (*model.Session, error) {
|
||||||
return session, nil
|
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
|
// 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) {
|
func (a *App) Login(username string, email string, password string, mfaToken string) (string, error) {
|
||||||
var user *model.User
|
var user *model.User
|
||||||
|
|
|
@ -13,6 +13,7 @@ func (s *SQLStore) getUserByCondition(condition sq.Eq) (*model.User, error) {
|
||||||
query := s.getQueryBuilder().
|
query := s.getQueryBuilder().
|
||||||
Select("id", "username", "email", "password", "mfa_secret", "auth_service", "auth_data", "props", "create_at", "update_at", "delete_at").
|
Select("id", "username", "email", "password", "mfa_secret", "auth_service", "auth_data", "props", "create_at", "update_at", "delete_at").
|
||||||
From("users").
|
From("users").
|
||||||
|
Where(sq.Eq{"delete_at": 0}).
|
||||||
Where(condition)
|
Where(condition)
|
||||||
row := query.QueryRow()
|
row := query.QueryRow()
|
||||||
user := model.User{}
|
user := model.User{}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React, {useState} from 'react'
|
import React from 'react'
|
||||||
import {IntlProvider} from 'react-intl'
|
import {IntlProvider} from 'react-intl'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -11,6 +11,7 @@ import {
|
||||||
} from 'react-router-dom'
|
} from 'react-router-dom'
|
||||||
|
|
||||||
import client from './octoClient'
|
import client from './octoClient'
|
||||||
|
import {IUser, UserContext} from './user'
|
||||||
|
|
||||||
import {getCurrentLanguage, getMessages, storeLanguage} from './i18n'
|
import {getCurrentLanguage, getMessages, storeLanguage} from './i18n'
|
||||||
|
|
||||||
|
@ -20,40 +21,65 @@ import LoginPage from './pages/loginPage'
|
||||||
import RegisterPage from './pages/registerPage'
|
import RegisterPage from './pages/registerPage'
|
||||||
import BoardPage from './pages/boardPage'
|
import BoardPage from './pages/boardPage'
|
||||||
|
|
||||||
export default function App(): JSX.Element {
|
type State = {
|
||||||
const [language, setLanguage] = useState(getCurrentLanguage())
|
language: string,
|
||||||
const setAndStoreLanguage = (lang: string) => {
|
user: IUser|null,
|
||||||
storeLanguage(lang)
|
initialLoad: boolean,
|
||||||
setLanguage(lang)
|
}
|
||||||
}
|
|
||||||
return (
|
export default class App extends React.PureComponent<unknown, State> {
|
||||||
<IntlProvider
|
constructor(props: unknown) {
|
||||||
locale={language}
|
super(props)
|
||||||
messages={getMessages(language)}
|
this.state = {
|
||||||
>
|
language: getCurrentLanguage(),
|
||||||
<FlashMessages milliseconds={2000}/>
|
user: null,
|
||||||
<Router>
|
initialLoad: false,
|
||||||
<div id='frame'>
|
}
|
||||||
<div id='main'>
|
}
|
||||||
<Switch>
|
|
||||||
<Route path='/login'>
|
public componentDidMount(): void {
|
||||||
<LoginPage/>
|
client.getMe().then((user: IUser|null) => {
|
||||||
</Route>
|
this.setState({user, initialLoad: true})
|
||||||
<Route path='/register'>
|
})
|
||||||
<RegisterPage/>
|
}
|
||||||
</Route>
|
|
||||||
<Route path='/'>
|
setAndStoreLanguage = (lang: string): void => {
|
||||||
{!client.token && <Redirect to='/login'/>}
|
storeLanguage(lang)
|
||||||
<BoardPage setLanguage={setAndStoreLanguage}/>
|
this.setState({language: lang})
|
||||||
</Route>
|
}
|
||||||
<Route path='/board'>
|
|
||||||
{!client.token && <Redirect to='/login'/>}
|
public render(): JSX.Element {
|
||||||
<BoardPage setLanguage={setAndStoreLanguage}/>
|
return (
|
||||||
</Route>
|
<IntlProvider
|
||||||
</Switch>
|
locale={this.state.language}
|
||||||
</div>
|
messages={getMessages(this.state.language)}
|
||||||
</div>
|
>
|
||||||
</Router>
|
<UserContext.Provider value={this.state.user}>
|
||||||
</IntlProvider>
|
<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.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import {IBlock, IMutableBlock} from './blocks/block'
|
import {IBlock, IMutableBlock} from './blocks/block'
|
||||||
|
import {IUser} from './user'
|
||||||
import {Utils} from './utils'
|
import {Utils} from './utils'
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -58,6 +59,13 @@ class OctoClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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[]> {
|
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, {headers: this.headers()})
|
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
|
||||||
|
|
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}
|
Loading…
Reference in a new issue