ソースを参照

Merge branch 'master' of https://github.com/porter-dev/porter into kubeconfig

mergin
Alexander Belanger 5 年 前
コミット
eeae8a228a

+ 2 - 0
.gitignore

@@ -1 +1,3 @@
 .DS_Store
+.env
+app

ファイルの差分が大きいため隠しています
+ 7849 - 2411
dashboard/package-lock.json


+ 15 - 1
dashboard/package.json

@@ -3,6 +3,17 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@testing-library/jest-dom": "^4.2.4",
+    "@testing-library/react": "^9.3.2",
+    "@testing-library/user-event": "^7.1.2",
+    "@types/jest": "^24.0.0",
+    "@types/node": "^12.12.62",
+    "@types/react": "^16.9.49",
+    "@types/react-dom": "^16.9.8",
+    "@types/react-modal": "^3.10.6",
+    "@types/react-router": "^5.1.8",
+    "@types/react-router-dom": "^5.1.5",
+    "@types/styled-components": "^5.1.3",
     "ace-builds": "^1.4.12",
     "axios": "^0.20.0",
     "dotenv": "^8.2.0",
@@ -10,7 +21,10 @@
     "react-ace": "^9.1.3",
     "react-dom": "^16.13.1",
     "react-modal": "^3.11.2",
-    "styled-components": "^5.2.0"
+    "react-router-dom": "^5.2.0",
+    "react-scripts": "3.4.3",
+    "styled-components": "^5.2.0",
+    "typescript": "~3.7.2"
   },
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",

+ 1 - 20
dashboard/src/App.tsx

@@ -8,9 +8,6 @@ type PropsType = {
 };
 
 type StateType = {
-  isLoading: boolean,
-  isLoggedIn: boolean
-  uninitialized: boolean,
 };
 
 export default class App extends Component<PropsType, StateType> {
@@ -21,20 +18,4 @@ export default class App extends Component<PropsType, StateType> {
       </ContextProvider>
     );
   }
-}
-
-const GlobalStyle = createGlobalStyle`
-  * {
-    box-sizing: border-box;
-  }
-`;
-
-const StyledApp = styled.div`
-  height: 100vh;
-  width: 100vw;
-  position: fixed;
-  top: 0;
-  left: 0;
-  background: #24272a;
-  color: white;
-`;
+}

+ 2 - 5
dashboard/src/main/Login.tsx

@@ -34,15 +34,12 @@ export default class Login extends Component<PropsType, StateType> {
     if (!emailRegex.test(email)) {
       this.setState({ emailError: true });
     } else {
-      
       // Attempt user login
-      api.logInUser('<token>', {
+      api.logInUser('', {
         email: email,
         password: password
       }, {}, (err: any, res: any) => {
         // TODO: case and set credential error
-
-        console.log(err)
         err ? setCurrentError(JSON.stringify(err)) : authenticate();
       });
     }
@@ -65,7 +62,7 @@ export default class Login extends Component<PropsType, StateType> {
       );
     }
   }
-
+  
   render() {
     let { email, password, credentialError, emailError } = this.state;
 

+ 57 - 22
dashboard/src/main/Main.tsx

@@ -2,48 +2,46 @@ import React, { Component } from 'react';
 import styled, { createGlobalStyle } from 'styled-components';
 import close from '../assets/close.png';
 
+import api from '../shared/api';
 import { Context } from '../shared/Context';
 
 import Login from './Login';
 import Register from './Register';
 import Home from './home/Home';
+import { BrowserRouter, Route, Redirect, Switch } from 'react-router-dom';
 
 type PropsType = {
 };
 
 type StateType = {
   isLoading: boolean,
-  isLoggedIn: boolean
-  uninitialized: boolean,
+  isLoggedIn: boolean,
+  initialized: boolean,
 };
 
 export default class Main extends Component<PropsType, StateType> {
+  
   state = {
     isLoading: false,
-    isLoggedIn: true,
-    uninitialized: true,
-  };
+    isLoggedIn : false,
+    initialized: false
+  }
 
   componentDidMount() {
-    // Check if Porter has already been initialized
-    if (false) {
-      // Check if user is logged in
-      if (false) {
-        this.setState({ isLoggedIn: true });
+    localStorage.getItem("init") == 'true' ? this.setState({initialized: true}) : this.setState({initialized: false})
+    api.checkAuth('', {}, {}, (err: any, res: any) => {
+      if (res.data) {
+        this.setState({ isLoggedIn: true, initialized: true})
+      } else {
+        this.setState({ isLoggedIn: false })
       }
-    }
+    });
   }
 
-  renderContents = (): JSX.Element => {
-    if (this.state.isLoading) {
-      return <h1>Loading...</h1>
-    } else if (this.state.isLoggedIn) {
-      return <Home logOut={() => this.setState({ isLoggedIn: false })} />
-    } else if (this.state.uninitialized) {
-      return <Register authenticate={() => this.setState({ isLoggedIn: true })} />
-    }
-    return <Login authenticate={() => this.setState({ isLoggedIn: true })} />
-  };
+  initialize = () => {
+    this.setState({isLoggedIn: true, initialized: true});
+    localStorage.setItem('init', 'true');
+  }
 
   renderCurrentError = (): JSX.Element | undefined => {
     if (this.context.currentError) {
@@ -60,9 +58,46 @@ export default class Main extends Component<PropsType, StateType> {
 
   render() {
     return (
+
       <StyledMain>
         <GlobalStyle />
-        {this.renderContents()}
+        <BrowserRouter>
+          <Switch>
+            <Route path='/login' render={() => {
+              if (!this.state.isLoggedIn && this.state.initialized) {
+                return <Login authenticate={() => this.setState({ isLoggedIn: true, initialized: true })} />
+              } else {
+                return <Redirect to='/' />
+              }
+            }} />
+
+            <Route path='/register' render={() => {
+              if (!this.state.initialized) {
+                return <Register authenticate={this.initialize} />
+              } else {
+                return <Redirect to='/' />
+              }
+            }} />
+
+            <Route path='/dashboard' render={() => {
+              if (this.state.isLoggedIn && this.state.initialized) {
+                return <Home logOut={() => this.setState({ isLoggedIn: false, initialized: true })} />
+              } else {
+                return <Redirect to='/' />
+              }
+            }}/>
+
+            <Route path='/' render={() => {
+              if (this.state.isLoggedIn) {
+                return <Redirect to='/dashboard'/>
+              } else if (this.state.initialized) {
+                return <Redirect to='/login'/>
+              } else {
+                return <Redirect to='/register' />
+              }
+            }}/>
+          </Switch>
+        </BrowserRouter>
         {this.renderCurrentError()}
       </StyledMain>
     );

+ 1 - 0
dashboard/src/main/Register.tsx

@@ -48,6 +48,7 @@ export default class Register extends Component<PropsType, StateType> {
         email: email,
         password: password
       }, {}, (err: any, res: any) => {
+        console.log('err',err)
         err ? setCurrentError(JSON.stringify(err)) : authenticate();
       });
     } 

+ 1 - 0
dashboard/src/shared/Context.tsx

@@ -30,6 +30,7 @@ class ContextProvider extends Component {
     },
     currentError: null as string | null,
     setCurrentError: (currentError: string): void => {
+      console.log('setting err', currentError)
       this.setState({ currentError });
     },
     currentCluster: null as string | null,

+ 5 - 2
dashboard/src/shared/api.tsx

@@ -9,17 +9,19 @@ import { baseApi } from './baseApi';
  * @param {(err: Object, res: Object) => void} callback - Callback function.
  */
 
+const checkAuth = baseApi('GET', '/api/auth/check');
+
 const registerUser = baseApi<{ 
   email: string, 
   password: string
-}>('POST', '/api/register');
+}>('POST', '/api/users');
 
 const logInUser = baseApi<{
   email: string,
   password: string
 }>('POST', '/api/login');
 
-const logOutUser = baseApi<{}>('GET', '/api/logout');
+const logOutUser = baseApi('POST', '/api/logout');
 
 const getUser = baseApi<{}, { id: number }>('GET', pathParams => {
   return `/api/users/${pathParams.id}`;
@@ -42,6 +44,7 @@ const getAllClusters = baseApi<{}, { id: number }>('GET', pathParams => {
 
 // Bundle export to allow default api import (api.<method> is more readable)
 export default {
+  checkAuth,
   registerUser,
   logInUser,
   logOutUser,

+ 7 - 13
dashboard/src/shared/baseApi.tsx

@@ -1,12 +1,8 @@
 import axios from 'axios';
 
 // Partial function that accepts a generic params type and returns an api method
-export const baseApi = <T extends {}, S = {}>(requestType: string, endpoint: ((pathParams: S) => string) | string, baseUrlOverride?: string) => {
+export const baseApi = <T extends {}, S = {}>(requestType: string, endpoint: ((pathParams: S) => string) | string) => {
   return (token: string, params: T, pathParams: S, callback?: (err: any, res: any) => void) => {
-    let baseUrl = (process as any).env.API_SERVER;
-    if (baseUrlOverride) {
-      baseUrl = baseUrlOverride;
-    }
 
     // Generate endpoint literal
     let endpointString: ((pathParams: S) => string) | string;
@@ -18,8 +14,8 @@ export const baseApi = <T extends {}, S = {}>(requestType: string, endpoint: ((p
 
     // Handle request type (can refactor)
     if (requestType === 'POST') {
-      axios.post(`https://${baseUrl + endpointString}`, params, {
-        headers: {
+      axios.post(endpointString, params, {
+      headers: {
           Authorization: `Bearer ${token}`
         }
       })
@@ -30,7 +26,7 @@ export const baseApi = <T extends {}, S = {}>(requestType: string, endpoint: ((p
         callback && callback(err, null);
       });
     } else if (requestType === 'PUT') {
-      axios.put(`https://${baseUrl + endpointString}`, params, {
+      axios.put(endpointString, params, {
         headers: {
           Authorization: `Bearer ${token}`
         }
@@ -42,14 +38,12 @@ export const baseApi = <T extends {}, S = {}>(requestType: string, endpoint: ((p
         callback && callback(err, null);
       });
     } else {
-      axios.get(`https://${baseUrl + endpoint}`, {
-        headers: {
-          Authorization: `Bearer ${token}`
-        },
+      axios.get(endpointString, {
+        withCredentials: true,
         params
       })
       .then(res => {
-        callback && callback(null, res.data);
+        callback && callback(null, res);
       })
       .catch(err => {
         callback && callback(err, null);

+ 4 - 0
dashboard/webpack.config.js

@@ -52,6 +52,10 @@ module.exports = () => {
     output: {
       filename: 'bundle.js',
       path: path.resolve(__dirname, 'build'),
+      publicPath: '/'
+    },
+    devServer: {
+      historyApiFallback: true,
     },
     plugins: [
       new HtmlWebpackPlugin({

+ 19 - 3
docker-compose.yaml

@@ -24,6 +24,22 @@ services:
     volumes:
       - db:/var/lib/postgresql/data
 
-volumes:
-  db:
-  metabase:
+  # metabase:
+  #   image: metabase/metabase
+  #   restart: always
+  #   ports: 
+  #     - 3000:3000
+  #   volumes: 
+  #     - metabase:/metabase-data
+  #   environment:
+  #     MB_DB_TYPE: postgres
+  #     MB_DB_DBNAME: porter
+  #     MB_DB_PORT: 5432
+  #     MB_DB_USER: porter
+  #     MB_DB_PASS: porter
+  #     MB_DB_HOST: postgres
+  #   depends_on:
+  #     - postgres
+  #   volumes:
+  #     db:
+  #     metabase:

+ 2 - 1
docker/.env

@@ -9,4 +9,5 @@ DB_HOST=postgres
 DB_PORT=5432
 DB_USER=porter
 DB_PASS=porter
-DB_NAME=porter
+DB_NAME=porter
+COOKIE_SECRETS=secret

+ 68 - 0
docs/API.md

@@ -10,6 +10,8 @@
   - [`GET /api/users/{id}/clusters`](#get-apiusersidclusters)
   - [`GET /api/users/{id}/clusters/all`](#get-apiusersidclustersall)
   - [`POST /api/users`](#post-apiusers)
+  - [`POST /api/login`](#post-apilogin)
+  - [`POST /api/logout`](#post-apilogout)
   - [`PUT /api/users/{id}`](#put-apiusersid)
   - [`DELETE /api/users/{id}`](#delete-apiusersid)
 
@@ -285,6 +287,72 @@ User{
     }
     ```
 
+#### `POST /api/login`
+
+**Description:** Logs a user in via email and password.
+
+**URL parameters:** N/A
+
+**Query parameters:** N/A
+
+**Request Body**: 
+
+```js
+{
+    "email": String,
+    "password": String,
+}
+```
+
+**Successful Response Body**: N/A
+
+**Successful Status Code**: `200`
+
+**Errors:**
+
+- Email not registered
+  - Status Code: `401`
+  - Request Body:
+    ```json
+    {
+        "code": 401,
+        "errors": ["email not registered"]
+    }
+    ```
+
+- Incorrect password
+  - Status Code: `401`
+  - Request Body:
+    ```json
+    {
+        "code":401,
+        "errors":["incorrect password"]
+    }
+    ```
+
+#### `POST /api/logout`
+
+**Description:** Logs a user out by detaching a user from the cookie-based session. 
+
+**URL parameters:** N/A
+
+**Query parameters:** N/A
+
+**Request Body**: N/A
+
+**Successful Response Body**: N/A
+
+**Successful Status Code**: `200`
+
+**Errors:**
+
+- Not logged in
+  - Status Code: `403`
+  - Request Body:
+    ```sh
+    "Forbidden"
+    ```
+
 #### `PUT /api/users/{id}`
 
 **Description:** Updates an existing user.

+ 1 - 0
go.mod

@@ -11,6 +11,7 @@ require (
 	github.com/evanphx/json-patch v4.9.0+incompatible // indirect
 	github.com/fatih/color v1.9.0 // indirect
 	github.com/go-chi/chi v4.1.2+incompatible
+	github.com/go-chi/cors v1.1.1
 	github.com/go-playground/locales v0.13.0
 	github.com/go-playground/universal-translator v0.17.0
 	github.com/go-playground/validator/v10 v10.3.0

+ 2 - 0
go.sum

@@ -254,6 +254,8 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0
 github.com/go-chi/chi v1.0.0 h1:s/kv1cTXfivYjdKJdyUzNGyAWZ/2t7duW1gKn5ivu+c=
 github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
 github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
+github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
+github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=

+ 0 - 62
internal/auth/example/authExample.go

@@ -1,62 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"net/http"
-
-	"github.com/porter-dev/porter/internal/config"
-	"github.com/porter-dev/porter/internal/repository/gorm"
-
-	dbConn "github.com/porter-dev/porter/internal/adapter"
-	sessionstore "github.com/porter-dev/porter/internal/auth"
-)
-
-var appConf = config.FromEnv()
-
-var db, dbErr = dbConn.New(&appConf.Db)
-
-var (
-	store, _ = sessionstore.NewStore(gorm.NewRepository(db), appConf.Server)
-)
-
-func secret(w http.ResponseWriter, r *http.Request) {
-
-	session, _ := store.Get(r, "cookie-name")
-	fmt.Println(session.Values["authenticated"])
-
-	// Check if user is authenticated
-	if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
-		http.Error(w, "Forbidden", http.StatusForbidden)
-		return
-	}
-
-	// Print secret message
-	fmt.Fprintln(w, "The cake is a lie!")
-}
-
-func login(w http.ResponseWriter, r *http.Request) {
-	session, _ := store.Get(r, "cookie-name")
-
-	// Authentication goes here
-	// ...
-
-	// Set user as authenticated
-	session.Values["authenticated"] = true
-	session.Save(r, w)
-}
-
-func logout(w http.ResponseWriter, r *http.Request) {
-	session, _ := store.Get(r, "cookie-name")
-
-	// Revoke users authentication
-	session.Values["authenticated"] = false
-	session.Save(r, w)
-}
-
-func main() {
-	http.HandleFunc("/secret", secret)
-	http.HandleFunc("/login", login)
-	http.HandleFunc("/logout", logout)
-
-	http.ListenAndServe(":8080", nil)
-}

+ 3 - 1
internal/auth/sessionstore.go

@@ -111,7 +111,9 @@ func (store *PGStore) save(session *sessions.Session) error {
 
 // NewStore takes an initialized db and session key pairs to create a session-store in postgres db.
 func NewStore(repo *repository.Repository, conf config.ServerConf) (*PGStore, error) {
-	keyPairs := conf.CookieSecrets
+	keyPairs := [][]byte{
+		conf.CookieSecret,
+	}
 
 	dbStore := &PGStore{
 		Codecs: securecookie.CodecsFromPairs(keyPairs...),

+ 6 - 6
internal/config/config.go

@@ -16,12 +16,12 @@ type Conf struct {
 
 // ServerConf is the server configuration
 type ServerConf struct {
-	Port          int           `env:"SERVER_PORT,default=8080"`
-	CookieName    string        `env:"COOKIE_NAME,default=porter"`
-	CookieSecrets [][]byte      `env:"COOKIE_SECRETS,default=secret"`
-	TimeoutRead   time.Duration `env:"SERVER_TIMEOUT_READ,default=5s"`
-	TimeoutWrite  time.Duration `env:"SERVER_TIMEOUT_WRITE,default=10s"`
-	TimeoutIdle   time.Duration `env:"SERVER_TIMEOUT_IDLE,default=15s"`
+	Port         int           `env:"SERVER_PORT,default=8080"`
+	CookieName   string        `env:"COOKIE_NAME,default=porter"`
+	CookieSecret []byte        `env:"COOKIE_SECRETS,default=secret"`
+	TimeoutRead  time.Duration `env:"SERVER_TIMEOUT_READ,default=5s"`
+	TimeoutWrite time.Duration `env:"SERVER_TIMEOUT_WRITE,default=10s"`
+	TimeoutIdle  time.Duration `env:"SERVER_TIMEOUT_IDLE,default=15s"`
 }
 
 // DBConf is the database configuration: if generated from environment variables,

+ 38 - 2
server/api/user_handler.go

@@ -3,11 +3,13 @@ package api
 import (
 	"encoding/json"
 	"errors"
+	"fmt"
 	"net/http"
 	"strconv"
 	"strings"
 
 	"github.com/porter-dev/porter/internal/kubernetes"
+	"golang.org/x/crypto/bcrypt"
 
 	"gorm.io/gorm"
 
@@ -15,7 +17,6 @@ import (
 	"github.com/porter-dev/porter/internal/forms"
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/repository"
-	"golang.org/x/crypto/bcrypt"
 )
 
 // Enumeration of user API error codes, represented as int64
@@ -53,6 +54,25 @@ func (app *App) HandleCreateUser(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
+// HandleAuthCheck checks whether current session is authenticated.
+func (app *App) HandleAuthCheck(w http.ResponseWriter, r *http.Request) {
+	session, err := app.store.Get(r, app.cookieName)
+	cook, _ := r.Cookie("porter")
+	fmt.Println("cooki", cook)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+	}
+
+	if auth, ok := session.Values["authenticated"].(bool); !auth || !ok {
+		app.logger.Info().Msgf(strconv.FormatBool(auth))
+		w.WriteHeader(http.StatusOK)
+		w.Write([]byte("false"))
+		return
+	}
+	w.WriteHeader(http.StatusOK)
+	w.Write([]byte("true"))
+}
+
 // HandleLoginUser checks the request header for cookie and validates the user.
 func (app *App) HandleLoginUser(w http.ResponseWriter, r *http.Request) {
 	session, err := app.store.Get(r, app.cookieName)
@@ -62,7 +82,6 @@ func (app *App) HandleLoginUser(w http.ResponseWriter, r *http.Request) {
 	}
 
 	form := &forms.LoginUserForm{}
-
 	// decode from JSON to form value
 	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
 		app.handleErrorFormDecoding(err, ErrUserDecode, w)
@@ -92,6 +111,23 @@ func (app *App) HandleLoginUser(w http.ResponseWriter, r *http.Request) {
 	// Set user as authenticated
 	session.Values["authenticated"] = true
 	session.Values["user_id"] = storedUser.ID
+	if err := session.Save(r, w); err != nil {
+		app.logger.Warn().Err(err)
+	}
+
+	w.WriteHeader(http.StatusOK)
+}
+
+// HandleLogoutUser detaches the user from the session
+func (app *App) HandleLogoutUser(w http.ResponseWriter, r *http.Request) {
+	session, err := app.store.Get(r, app.cookieName)
+
+	if err != nil {
+		app.handleErrorDataRead(err, ErrUserDataRead, w)
+	}
+
+	session.Values["authenticated"] = false
+	session.Values["user_id"] = nil
 	session.Save(r, w)
 	w.WriteHeader(http.StatusOK)
 }

+ 45 - 0
server/api/user_handler_test.go

@@ -340,6 +340,51 @@ func TestHandleLoginUser(t *testing.T) {
 	testUserRequests(t, loginUserTests, true)
 }
 
+var logoutUserTests = []*userTest{
+	&userTest{
+		initializers: []func(tester *tester){
+			initUserDefault,
+		},
+		msg:      "Logout user successful",
+		method:   "POST",
+		endpoint: "/api/logout",
+		body: `{
+			"email": "belanger@getporter.dev",
+			"password": "hello"
+		}`,
+		expStatus: http.StatusOK,
+		expBody:   ``,
+		useCookie: true,
+		validators: []func(c *userTest, tester *tester, t *testing.T){
+			func(c *userTest, tester *tester, t *testing.T) {
+				req, err := http.NewRequest(
+					"GET",
+					"/api/users/1",
+					strings.NewReader(""),
+				)
+
+				req.AddCookie(tester.cookie)
+
+				if err != nil {
+					t.Fatal(err)
+				}
+
+				rr2 := httptest.NewRecorder()
+				tester.router.ServeHTTP(rr2, req)
+
+				if status := rr2.Code; status != http.StatusForbidden {
+					t.Errorf("%s, handler returned wrong status: got %v want %v",
+						"validator failed", status, http.StatusForbidden)
+				}
+			},
+		},
+	},
+}
+
+func TestHandleLogoutUser(t *testing.T) {
+	testUserRequests(t, logoutUserTests, true)
+}
+
 var readUserTests = []*userTest{
 	&userTest{
 		initializers: []func(tester *tester){

+ 2 - 0
server/router/router.go

@@ -26,6 +26,8 @@ func New(a *api.App, store *sessionstore.PGStore, cookieName string) *chi.Mux {
 		r.Method("PUT", "/users/{id}", auth.DoesUserIDMatch(requestlog.NewHandler(a.HandleUpdateUser, l)))
 		r.Method("DELETE", "/users/{id}", auth.DoesUserIDMatch(requestlog.NewHandler(a.HandleDeleteUser, l)))
 		r.Method("POST", "/login", requestlog.NewHandler(a.HandleLoginUser, l))
+		r.Method("GET", "/auth/check", requestlog.NewHandler(a.HandleAuthCheck, l))
+		r.Method("POST", "/logout", auth.BasicAuthenticate(requestlog.NewHandler(a.HandleLogoutUser, l)))
 	})
 
 	return r

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません