More work on auth

This commit is contained in:
Jesús Espino 2020-12-07 20:40:16 +01:00
parent 4cc6ecbc64
commit e5941d6440
6 changed files with 168 additions and 44 deletions

View file

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

View file

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

View file

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

View file

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

View file

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