diff --git a/doc/api.oa3 b/doc/api.oa3 index 689e6670..ec508721 100644 --- a/doc/api.oa3 +++ b/doc/api.oa3 @@ -84,8 +84,13 @@ paths: - admin description: Get node configuration. Access granted to admin username and password. operationId: get-node-config - security: - - basicAuth: [] + parameters: + - name: token + in: query + description: token for admin access + required: true + schema: + type: string responses: '200': description: success @@ -102,8 +107,13 @@ paths: - admin description: Set node config. Access granted to admin username and password. operationId: set-node-config - security: - - basicAuth: [] + parameters: + - name: token + in: query + description: token for admin access + required: true + schema: + type: string responses: '200': description: success @@ -118,8 +128,13 @@ paths: - admin description: Get list of accounts hosted on node. Access granted to admin username and password. operationId: get-node-accounts - security: - - basicAuth: [] + parameters: + - name: token + in: query + description: token for admin access + required: true + schema: + type: string responses: '200': description: successful operation @@ -140,8 +155,13 @@ paths: - admin description: Gernerate a url for creating a new account. Access granted to admin username and password. operationId: add-node-account - security: - - basicAuth: [] + parameters: + - name: token + in: query + description: token for admin access + required: true + schema: + type: string responses: '201': description: success @@ -160,8 +180,6 @@ paths: - admin description: Get profile image of node account. operationId: get-node-account-image - security: - - basicAuth: [] parameters: - name: accountId in: path @@ -169,6 +187,12 @@ paths: required: true schema: type: string + - name: token + in: query + description: token for admin access + required: true + schema: + type: string responses: '200': description: successful operation @@ -199,6 +223,12 @@ paths: required: true schema: type: string + - name: token + in: query + description: token for admin access + required: true + schema: + type: string responses: '200': description: successful operation diff --git a/net/server/internal/api_addNodeAccount.go b/net/server/internal/api_addNodeAccount.go index d9f919e0..b9b942f4 100644 --- a/net/server/internal/api_addNodeAccount.go +++ b/net/server/internal/api_addNodeAccount.go @@ -10,8 +10,8 @@ import ( func AddNodeAccount(w http.ResponseWriter, r *http.Request) { - if err := AdminLogin(r); err != nil { - ErrResponse(w, http.StatusUnauthorized, err) + if code, err := ParamAdminToken(r); err != nil { + ErrResponse(w, code, err) return } diff --git a/net/server/internal/api_getNodeAccountImage.go b/net/server/internal/api_getNodeAccountImage.go index 2e954a7d..6696c542 100644 --- a/net/server/internal/api_getNodeAccountImage.go +++ b/net/server/internal/api_getNodeAccountImage.go @@ -22,8 +22,8 @@ func GetNodeAccountImage(w http.ResponseWriter, r *http.Request) { return } - if err := AdminLogin(r); err != nil { - ErrResponse(w, http.StatusUnauthorized, err) + if code, err := ParamAdminToken(r); err != nil { + ErrResponse(w, code, err) return } diff --git a/net/server/internal/api_getNodeAccounts.go b/net/server/internal/api_getNodeAccounts.go index c3962e2c..04cae725 100644 --- a/net/server/internal/api_getNodeAccounts.go +++ b/net/server/internal/api_getNodeAccounts.go @@ -7,8 +7,8 @@ import ( func GetNodeAccounts(w http.ResponseWriter, r *http.Request) { - if err := AdminLogin(r); err != nil { - ErrResponse(w, http.StatusUnauthorized, err) + if code, err := ParamAdminToken(r); err != nil { + ErrResponse(w, code, err) return } diff --git a/net/server/internal/api_getNodeConfig.go b/net/server/internal/api_getNodeConfig.go index 6463c069..c09b4d63 100644 --- a/net/server/internal/api_getNodeConfig.go +++ b/net/server/internal/api_getNodeConfig.go @@ -7,8 +7,8 @@ import ( func GetNodeConfig(w http.ResponseWriter, r *http.Request) { // validate login - if err := AdminLogin(r); err != nil { - ErrResponse(w, http.StatusUnauthorized, err) + if code, err := ParamAdminToken(r); err != nil { + ErrResponse(w, code, err) return } diff --git a/net/server/internal/api_removeNodeAccount.go b/net/server/internal/api_removeNodeAccount.go index caf0d6b2..2f024165 100644 --- a/net/server/internal/api_removeNodeAccount.go +++ b/net/server/internal/api_removeNodeAccount.go @@ -20,8 +20,8 @@ func RemoveNodeAccount(w http.ResponseWriter, r *http.Request) { return } - if err := AdminLogin(r); err != nil { - ErrResponse(w, http.StatusUnauthorized, err) + if code, err := ParamAdminToken(r); err != nil { + ErrResponse(w, code, err) return } diff --git a/net/server/internal/api_setNodeAccount.go b/net/server/internal/api_setNodeAccount.go index 866f75ba..75b9c414 100644 --- a/net/server/internal/api_setNodeAccount.go +++ b/net/server/internal/api_setNodeAccount.go @@ -20,8 +20,8 @@ func SetNodeAccount(w http.ResponseWriter, r *http.Request) { return } - if res = AdminLogin(r); res != nil { - ErrResponse(w, http.StatusUnauthorized, res) + if code, res := ParamAdminToken(r); res != nil { + ErrResponse(w, code, res) return } diff --git a/net/server/internal/api_setNodeConfig.go b/net/server/internal/api_setNodeConfig.go index 96bc8825..7e854aa1 100644 --- a/net/server/internal/api_setNodeConfig.go +++ b/net/server/internal/api_setNodeConfig.go @@ -10,8 +10,8 @@ import ( func SetNodeConfig(w http.ResponseWriter, r *http.Request) { // validate login - if err := AdminLogin(r); err != nil { - ErrResponse(w, http.StatusUnauthorized, err) + if code, err := ParamAdminToken(r); err != nil { + ErrResponse(w, code, err) return } diff --git a/net/server/internal/api_setNodeStatus.go b/net/server/internal/api_setNodeStatus.go index 2f01cf45..e99f8871 100644 --- a/net/server/internal/api_setNodeStatus.go +++ b/net/server/internal/api_setNodeStatus.go @@ -20,18 +20,14 @@ func SetNodeStatus(w http.ResponseWriter, r *http.Request) { return } - username, password, res := BasicCredentials(r); - if res != nil || username != "admin" { - LogMsg("invalid credenitals"); + token := r.FormValue("token") + if token == "" { w.WriteHeader(http.StatusBadRequest) return } err = store.DB.Transaction(func(tx *gorm.DB) error { - if res := tx.Create(&store.Config{ConfigId: CONFIG_USERNAME, StrValue: username}).Error; res != nil { - return res - } - if res := tx.Create(&store.Config{ConfigId: CONFIG_PASSWORD, BinValue: password}).Error; res != nil { + if res := tx.Create(&store.Config{ConfigId: CONFIG_TOKEN, StrValue: token}).Error; res != nil { return res } if res := tx.Create(&store.Config{ConfigId: CONFIG_CONFIGURED, BoolValue: true}).Error; res != nil { diff --git a/net/server/internal/authUtil.go b/net/server/internal/authUtil.go index f97fdc02..3b2a5d0d 100644 --- a/net/server/internal/authUtil.go +++ b/net/server/internal/authUtil.go @@ -11,33 +11,6 @@ import ( "databag/internal/store" ) -func AdminLogin(r *http.Request) error { - - // extract request auth - username, password, ok := r.BasicAuth() - if !ok || username == "" || password == "" { - return errors.New("invalid credentials") - } - - // nothing to do if not configured - if !getBoolConfigValue(CONFIG_CONFIGURED, false) { - return errors.New("node not configured") - } - - // compare username - if getStrConfigValue(CONFIG_USERNAME, "") != username { - return errors.New("admin username error") - } - - // compare password - p := getBinConfigValue(CONFIG_PASSWORD, nil); - if bcrypt.CompareHashAndPassword(p, []byte(password)) != nil { - return errors.New("admin password error") - } - - return nil -} - func AccountLogin(r *http.Request) (*store.Account, error) { // extract request auth @@ -77,6 +50,28 @@ func BearerAccountToken(r *http.Request) (*store.AccountToken, error) { return &accountToken, nil } +func ParamAdminToken(r *http.Request) (int, error) { + + // parse authentication token + token := r.FormValue("token") + if token == "" { + return http.StatusUnauthorized, errors.New("token not set"); + } + + // nothing to do if not configured + if !getBoolConfigValue(CONFIG_CONFIGURED, false) { + return http.StatusUnauthorized, errors.New("node not configured") + } + + // compare password + value := getStrConfigValue(CONFIG_TOKEN, ""); + if (value != token) { + return http.StatusUnauthorized, errors.New("invalid admin token") + } + + return http.StatusOK, nil; +} + func ParamAgentToken(r *http.Request, detail bool) (*store.Account, int, error) { // parse authentication token diff --git a/net/server/internal/configUtil.go b/net/server/internal/configUtil.go index 796fbcc5..6bb4dd8b 100644 --- a/net/server/internal/configUtil.go +++ b/net/server/internal/configUtil.go @@ -9,8 +9,7 @@ import ( const CONFIG_OPENACCESS = "open_access" const CONFIG_ACCOUNTLIMIT = "account_limit" const CONFIG_CONFIGURED = "configured" -const CONFIG_USERNAME = "username" -const CONFIG_PASSWORD = "password" +const CONFIG_TOKEN = "token" const CONFIG_DOMAIN = "domain" const CONFIG_STORAGE = "storage" const CONFIG_ASSETPATH = "asset_path" diff --git a/net/server/internal/main_test.go b/net/server/internal/main_test.go index 792a00d5..2fdf560a 100644 --- a/net/server/internal/main_test.go +++ b/net/server/internal/main_test.go @@ -34,8 +34,7 @@ func TestMain(m *testing.M) { } // claim server - r, w, _ = NewRequest("PUT", "/admin/status", nil) - SetCredentials(r, "admin:pass"); + r, w, _ = NewRequest("PUT", "/admin/status?token=pass", nil) SetNodeStatus(w, r) if ReadResponse(w, nil) != nil { panic("failed to claim server") @@ -55,16 +54,14 @@ func TestMain(m *testing.M) { // config server config := NodeConfig{Domain: "databag.coredb.org", AccountLimit: 1024, OpenAccess: true, AccountStorage: 4096} - r, w, _ = NewRequest("PUT", "/admin/config", &config) - SetBasicAuth(r, "admin:pass") + r, w, _ = NewRequest("PUT", "/admin/config?token=pass", &config) SetNodeConfig(w, r) if ReadResponse(w, nil) != nil { panic("failed to set config") } // check config - r, w, _ = NewRequest("GET", "/admin/config", nil) - SetBasicAuth(r, "admin:pass") + r, w, _ = NewRequest("GET", "/admin/config?token=pass", nil) GetNodeConfig(w, r) var check NodeConfig if ReadResponse(w, &check) != nil { diff --git a/net/server/internal/testUtil.go b/net/server/internal/testUtil.go index e6b04c25..2c746b78 100644 --- a/net/server/internal/testUtil.go +++ b/net/server/internal/testUtil.go @@ -620,10 +620,9 @@ func AddTestAccount(username string) (guid string, token string, err error) { var login = username + ":pass" // get account token - if r, w, err= NewRequest("POST", "/admin/accounts", nil); err != nil { + if r, w, err= NewRequest("POST", "/admin/accounts?token=pass", nil); err != nil { return } - SetBasicAuth(r, "admin:pass") AddNodeAccount(w, r) if err = ReadResponse(w, &access); err != nil { return diff --git a/net/web/src/Admin/Dashboard/AccountItem/AccountItem.jsx b/net/web/src/Admin/Dashboard/AccountItem/AccountItem.jsx new file mode 100644 index 00000000..84401c22 --- /dev/null +++ b/net/web/src/Admin/Dashboard/AccountItem/AccountItem.jsx @@ -0,0 +1,4 @@ +export function AccountItem() { + return
ACCOUNT
+} + diff --git a/net/web/src/Admin/Dashboard/Dashboard.jsx b/net/web/src/Admin/Dashboard/Dashboard.jsx index 82829e64..24c6267e 100644 --- a/net/web/src/Admin/Dashboard/Dashboard.jsx +++ b/net/web/src/Admin/Dashboard/Dashboard.jsx @@ -1,7 +1,8 @@ import { DashboardWrapper, SettingsButton, AddButton, SettingsLayout } from './Dashboard.styled'; -import { Button, Modal, Input, InputNumber, Space } from 'antd'; -import { SettingOutlined, UserAddOutlined } from '@ant-design/icons'; +import { Button, Modal, Input, InputNumber, Space, List } from 'antd'; +import { SettingOutlined, UserAddOutlined, ReloadOutlined } from '@ant-design/icons'; import { useDashboard } from './useDashboard.hook'; +import { AccountItem } from './AccountItem/AccountItem'; export function Dashboard({ password, config }) { @@ -13,6 +14,10 @@ export function Dashboard({ password, config }) {
Accounts
+
+ } + onClick={() => actions.getAccounts()}> +
} onClick={() => actions.setShowSettings(true)}> @@ -21,10 +26,20 @@ export function Dashboard({ password, config }) { }>
+ +
+ ()} + /> +
actions.onSaveSettings()} onCancel={() => actions.setShowSettings(false)}> + okText="Save" onOk={() => actions.setSettings()} onCancel={() => actions.setShowSettings(false)}>
Federated Host: 
diff --git a/net/web/src/Admin/Dashboard/Dashboard.styled.js b/net/web/src/Admin/Dashboard/Dashboard.styled.js index 20aab109..a93502fc 100644 --- a/net/web/src/Admin/Dashboard/Dashboard.styled.js +++ b/net/web/src/Admin/Dashboard/Dashboard.styled.js @@ -17,33 +17,39 @@ export const DashboardWrapper = styled.div` border-radius: 4px; max-width: 500px; width: 50%; - } + max-height: 80%; - .header { - color: #444444; - display: flex; - flex-direction: row; - font-size: 20px; - border-bottom: 1px solid #444444; - } + .header { + color: #444444; + display: flex; + flex-direction: row; + font-size: 20px; + border-bottom: 1px solid #444444; + } - .label { - padding-right: 8px; - padding-left: 4px; - display: flex; - align-items: center; - } + .body { + min-height: 0; + overflow: auto; + } - .settings { - display: flex; - align-items: center; - } + .label { + padding-right: 8px; + padding-left: 4px; + display: flex; + align-items: center; + } - .add { - display: flex; - align-items: center; - justify-content: flex-end; - flex-grow: 1; + .settings { + display: flex; + align-items: center; + } + + .add { + display: flex; + align-items: center; + justify-content: flex-end; + flex-grow: 1; + } } `; diff --git a/net/web/src/Admin/Dashboard/useDashboard.hook.js b/net/web/src/Admin/Dashboard/useDashboard.hook.js index 06908554..b305f76c 100644 --- a/net/web/src/Admin/Dashboard/useDashboard.hook.js +++ b/net/web/src/Admin/Dashboard/useDashboard.hook.js @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; import { setNodeConfig } from 'api/setNodeConfig'; +import { getNodeAccounts } from 'api/getNodeAccounts'; export function useDashboard(password, config) { @@ -8,20 +9,14 @@ export function useDashboard(password, config) { storage: null, showSettings: false, busy: false, + loading: false, + accounts: [], }); const updateState = (value) => { setState((s) => ({ ...s, ...value })); } - useEffect(() => { - let storage = config.accountStorage / 1073741824; - if (storage > 1) { - storage = Math.ceil(storage); - } - updateState({ host: config.domain, storage: storage }); - }, []); - const actions = { setHost: (value) => { updateState({ host: value }); @@ -32,7 +27,7 @@ export function useDashboard(password, config) { setShowSettings: (value) => { updateState({ showSettings: value }); }, - onSaveSettings: async () => { + setSettings: async () => { if (!state.busy) { updateState({ busy: true }); try { @@ -47,8 +42,31 @@ export function useDashboard(password, config) { updateState({ busy: false }); } }, + getAccounts: async () => { + if (!state.loading) { + updateState({ loading: true }); + try { + let accounts = await getNodeAccounts(password); + updateState({ accounts }); + } + catch(err) { + console.log(err); + window.alert(err); + } + updateState({ loading: false }); + } + }, }; + useEffect(() => { + let storage = config.accountStorage / 1073741824; + if (storage > 1) { + storage = Math.ceil(storage); + } + updateState({ host: config.domain, storage: storage }); + actions.getAccounts(); + }, []); + return { state, actions }; } diff --git a/net/web/src/Admin/useAdmin.hook.js b/net/web/src/Admin/useAdmin.hook.js index c1cfa005..1a10ffa5 100644 --- a/net/web/src/Admin/useAdmin.hook.js +++ b/net/web/src/Admin/useAdmin.hook.js @@ -42,7 +42,8 @@ export function useAdmin() { setAccess: async () => { try { await setNodeStatus(state.token); - updateState({ access: state.token, unclaimed: false }); + let config = await getNodeConfig(state.token); + updateState({ access: state.token, unclaimed: false, config }); } catch(err) { console.log(err); diff --git a/net/web/src/api/getNodeAccounts.js b/net/web/src/api/getNodeAccounts.js new file mode 100644 index 00000000..7d7a43de --- /dev/null +++ b/net/web/src/api/getNodeAccounts.js @@ -0,0 +1,8 @@ +import { checkResponse, fetchWithTimeout } from './fetchUtil'; + +export async function getNodeAccounts(token) { + let accounts = await fetchWithTimeout(`/admin/accounts?token=${token}`, { method: 'GET' }); + checkResponse(accounts); + return await accounts.json(); +} + diff --git a/net/web/src/api/getNodeConfig.js b/net/web/src/api/getNodeConfig.js index 3005a989..fea820e3 100644 --- a/net/web/src/api/getNodeConfig.js +++ b/net/web/src/api/getNodeConfig.js @@ -1,10 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -var base64 = require('base-64'); -export async function getNodeConfig(password) { - let headers = new Headers() - headers.append('Authorization', 'Basic ' + base64.encode("admin:" + password)); - let config = await fetchWithTimeout(`/admin/config`, { method: 'GET', headers }); +export async function getNodeConfig(token) { + let config = await fetchWithTimeout(`/admin/config?token=${token}`, { method: 'GET' }); checkResponse(config); return await config.json(); } diff --git a/net/web/src/api/setNodeConfig.js b/net/web/src/api/setNodeConfig.js index 880f9b83..11da253e 100644 --- a/net/web/src/api/setNodeConfig.js +++ b/net/web/src/api/setNodeConfig.js @@ -1,11 +1,8 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -var base64 = require('base-64'); -export async function setNodeConfig(password, config) { +export async function setNodeConfig(token, config) { let body = JSON.stringify(config); - let headers = new Headers() - headers.append('Authorization', 'Basic ' + base64.encode("admin:" + password)); - let settings = await fetchWithTimeout(`/admin/config`, { method: 'PUT', headers, body }); + let settings = await fetchWithTimeout(`/admin/config?token=${token}`, { method: 'PUT', body }); checkResponse(settings); } diff --git a/net/web/src/api/setNodeStatus.js b/net/web/src/api/setNodeStatus.js index 9fffa313..2d2bc860 100644 --- a/net/web/src/api/setNodeStatus.js +++ b/net/web/src/api/setNodeStatus.js @@ -1,10 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -var base64 = require('base-64'); -export async function setNodeStatus(password) { - let headers = new Headers() - headers.append('Credentials', 'Basic ' + base64.encode("admin:" + password)); - let status = await fetchWithTimeout(`/admin/status`, { method: 'PUT', headers }); +export async function setNodeStatus(token) { + let status = await fetchWithTimeout(`/admin/status?token=${token}`, { method: 'PUT' }); checkResponse(status); }