mirror of
https://github.com/balzack/databag.git
synced 2025-02-12 03:29:16 +00:00
added magic access link
This commit is contained in:
parent
8ff747f90f
commit
74244b920d
45
doc/api.oa3
45
doc/api.oa3
@ -236,6 +236,33 @@ paths:
|
|||||||
description: account not found
|
description: account not found
|
||||||
'500':
|
'500':
|
||||||
description: internal server error
|
description: internal server error
|
||||||
|
|
||||||
|
/admin/accounts/{accountId}/auth:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- account
|
||||||
|
description: Generate token to reset authentication.
|
||||||
|
operationId: add-account-authentication
|
||||||
|
parameters:
|
||||||
|
- name: token
|
||||||
|
in: query
|
||||||
|
description: token for admin access
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: generated
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
'401':
|
||||||
|
description: invalid password
|
||||||
|
'500':
|
||||||
|
description: internal server error
|
||||||
|
|
||||||
|
/admin/accounts/{accountId}/status:
|
||||||
put:
|
put:
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
@ -581,24 +608,6 @@ paths:
|
|||||||
description: internal server error
|
description: internal server error
|
||||||
|
|
||||||
/account/auth:
|
/account/auth:
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- account
|
|
||||||
description: Generate token to reset authentication. Access granted to account's login and password.
|
|
||||||
operationId: add-account-authentication
|
|
||||||
security:
|
|
||||||
- basicAuth: []
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
description: generated
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
'401':
|
|
||||||
description: invalid password
|
|
||||||
'500':
|
|
||||||
description: internal server error
|
|
||||||
put:
|
put:
|
||||||
tags:
|
tags:
|
||||||
- account
|
- account
|
||||||
|
49
net/server/internal/api_addNodeAccountAccess.go
Normal file
49
net/server/internal/api_addNodeAccountAccess.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package databag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
"strconv"
|
||||||
|
"encoding/hex"
|
||||||
|
"databag/internal/store"
|
||||||
|
"github.com/theckman/go-securerandom"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddNodeAccountAccess(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
params := mux.Vars(r)
|
||||||
|
accountId, res := strconv.ParseUint(params["accountId"], 10, 32)
|
||||||
|
if res != nil {
|
||||||
|
ErrResponse(w, http.StatusBadRequest, res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if code, err := ParamAdminToken(r); err != nil {
|
||||||
|
ErrResponse(w, code, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, ret := securerandom.Bytes(APP_RESETSIZE)
|
||||||
|
if ret != nil {
|
||||||
|
ErrResponse(w, http.StatusInternalServerError, ret)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
token := hex.EncodeToString(data)
|
||||||
|
|
||||||
|
accountToken := store.AccountToken{
|
||||||
|
AccountID: uint(accountId),
|
||||||
|
TokenType: APP_TOKENRESET,
|
||||||
|
Token: token,
|
||||||
|
Expires: time.Now().Unix() + APP_RESETEXPIRE,
|
||||||
|
}
|
||||||
|
if err := store.DB.Create(&accountToken).Error; err != nil {
|
||||||
|
ErrResponse(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteResponse(w, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
71
net/server/internal/api_setAccountAccess.go
Normal file
71
net/server/internal/api_setAccountAccess.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package databag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"encoding/hex"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"databag/internal/store"
|
||||||
|
"github.com/theckman/go-securerandom"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetAccountAccess(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
token, _, res := AccessToken(r)
|
||||||
|
if res != nil || token.TokenType != APP_TOKENRESET {
|
||||||
|
ErrResponse(w, http.StatusUnauthorized, res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if token.Account == nil {
|
||||||
|
ErrResponse(w, http.StatusUnauthorized, errors.New("invalid reset token"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
account := token.Account;
|
||||||
|
|
||||||
|
// parse app data
|
||||||
|
var appData AppData
|
||||||
|
if err := ParseRequest(r, w, &appData); err != nil {
|
||||||
|
ErrResponse(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// gernate app token
|
||||||
|
data, err := securerandom.Bytes(APP_TOKENSIZE)
|
||||||
|
if err != nil {
|
||||||
|
ErrResponse(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
access := hex.EncodeToString(data)
|
||||||
|
|
||||||
|
// create app entry
|
||||||
|
app := store.App {
|
||||||
|
AccountID: account.Guid,
|
||||||
|
Name: appData.Name,
|
||||||
|
Description: appData.Description,
|
||||||
|
Image: appData.Image,
|
||||||
|
Url: appData.Url,
|
||||||
|
Token: access,
|
||||||
|
};
|
||||||
|
|
||||||
|
// save app and delete token
|
||||||
|
err = store.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
|
if res := tx.Create(&app).Error; res != nil {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if res := tx.Save(token.Account).Error; res != nil {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
if res := tx.Delete(token).Error; res != nil {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
});
|
||||||
|
if err != nil {
|
||||||
|
ErrResponse(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteResponse(w, account.Guid + "." + access)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -50,6 +50,25 @@ func BearerAccountToken(r *http.Request) (*store.AccountToken, error) {
|
|||||||
return &accountToken, nil
|
return &accountToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AccessToken(r *http.Request) (*store.AccountToken, int, error) {
|
||||||
|
|
||||||
|
// parse authentication token
|
||||||
|
token := r.FormValue("token")
|
||||||
|
if token == "" {
|
||||||
|
return nil, http.StatusUnauthorized, errors.New("token not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
// find token record
|
||||||
|
var accountToken store.AccountToken
|
||||||
|
if err := store.DB.Preload("Account").Where("token = ?", token).First(&accountToken).Error; err != nil {
|
||||||
|
return nil, http.StatusUnauthorized, err
|
||||||
|
}
|
||||||
|
if accountToken.Expires < time.Now().Unix() {
|
||||||
|
return nil, http.StatusUnauthorized, errors.New("expired token")
|
||||||
|
}
|
||||||
|
return &accountToken, http.StatusOK, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ParamAdminToken(r *http.Request) (int, error) {
|
func ParamAdminToken(r *http.Request) (int, error) {
|
||||||
|
|
||||||
// parse authentication token
|
// parse authentication token
|
||||||
|
@ -144,6 +144,13 @@ var routes = Routes{
|
|||||||
RemoveAccountApp,
|
RemoveAccountApp,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Route{
|
||||||
|
"SetAccountAccess",
|
||||||
|
strings.ToUpper("Put"),
|
||||||
|
"/account/access",
|
||||||
|
SetAccountAccess,
|
||||||
|
},
|
||||||
|
|
||||||
Route{
|
Route{
|
||||||
"SetAccountAuthentication",
|
"SetAccountAuthentication",
|
||||||
strings.ToUpper("Put"),
|
strings.ToUpper("Put"),
|
||||||
@ -200,6 +207,13 @@ var routes = Routes{
|
|||||||
SetNodeAccountStatus,
|
SetNodeAccountStatus,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Route{
|
||||||
|
"AddNodeAccountAccess",
|
||||||
|
strings.ToUpper("Post"),
|
||||||
|
"/admin/accounts/{accountId}/auth",
|
||||||
|
AddNodeAccountAccess,
|
||||||
|
},
|
||||||
|
|
||||||
Route{
|
Route{
|
||||||
"GetNodeAccounts",
|
"GetNodeAccounts",
|
||||||
strings.ToUpper("Get"),
|
strings.ToUpper("Get"),
|
||||||
|
@ -1,30 +1,38 @@
|
|||||||
import { Avatar } from 'avatar/Avatar';
|
import { Avatar } from 'avatar/Avatar';
|
||||||
import { AccountItemWrapper, DeleteButton, EnableButton, DisableButton, ResetButton } from './AccountItem.styled';
|
import { AccountItemWrapper, AccessLayout, DeleteButton, EnableButton, DisableButton, ResetButton } from './AccountItem.styled';
|
||||||
import { useAccountItem } from './useAccountItem.hook';
|
import { useAccountItem } from './useAccountItem.hook';
|
||||||
import { UserDeleteOutlined, UnlockOutlined, CloseCircleOutlined, CheckCircleOutlined } from '@ant-design/icons';
|
import { CopyOutlined, UserDeleteOutlined, UnlockOutlined, CloseCircleOutlined, CheckCircleOutlined } from '@ant-design/icons';
|
||||||
import { Tooltip } from 'antd';
|
import { Modal, Tooltip, Button } from 'antd';
|
||||||
|
|
||||||
export function AccountItem({ token, item }) {
|
export function AccountItem({ token, item, remove }) {
|
||||||
|
|
||||||
const { state, actions } = useAccountItem(token, item);
|
const { state, actions } = useAccountItem(token, item, remove);
|
||||||
|
|
||||||
|
const onClipboard = (value) => {
|
||||||
|
navigator.clipboard.writeText(value);
|
||||||
|
};
|
||||||
|
|
||||||
const Enable = () => {
|
const Enable = () => {
|
||||||
if (state.disabled) {
|
if (state.disabled) {
|
||||||
return (
|
return (
|
||||||
<Tooltip placement="topLeft" title="Enable Account">
|
<Tooltip placement="topLeft" title="Enable Account">
|
||||||
<EnableButton type="text" size="large" icon={<CheckCircleOutlined />}
|
<EnableButton type="text" size="large" icon={<CheckCircleOutlined />}
|
||||||
onClick={() => actions.setStatus(false)}></EnableButton>
|
loading={state.statusBusy} onClick={() => actions.setStatus(false)}></EnableButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Tooltip placement="topLeft" title="Disable Account">
|
<Tooltip placement="topLeft" title="Disable Account">
|
||||||
<DisableButton type="text" size="large" icon={<CloseCircleOutlined />}
|
<DisableButton type="text" size="large" icon={<CloseCircleOutlined />}
|
||||||
onClick={() => actions.setStatus(true)}></DisableButton>
|
loading={state.statusBusy} onClick={() => actions.setStatus(true)}></DisableButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const accessLink = () => {
|
||||||
|
return window.location.origin + '/#/login?access=' + state.accessToken;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountItemWrapper>
|
<AccountItemWrapper>
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
@ -35,14 +43,26 @@ export function AccountItem({ token, item }) {
|
|||||||
<div class="guid">{ state.guid }</div>
|
<div class="guid">{ state.guid }</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<Tooltip placement="topLeft" title="Reset Password">
|
<Tooltip placement="topLeft" title="Account Login Link">
|
||||||
<ResetButton type="text" size="large" icon={<UnlockOutlined />}></ResetButton>
|
<ResetButton type="text" size="large" icon={<UnlockOutlined />}
|
||||||
|
onClick={() => actions.setAccessLink()}></ResetButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Enable />
|
<Enable />
|
||||||
<Tooltip placement="topLeft" title="Delete Account">
|
<Tooltip placement="topLeft" title="Delete Account">
|
||||||
<DeleteButton type="text" size="large" icon={<UserDeleteOutlined />}></DeleteButton>
|
<DeleteButton type="text" size="large" icon={<UserDeleteOutlined />}
|
||||||
|
loading={state.removeBusy} onClick={() => actions.remove()}></DeleteButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
<Modal title="Access Link" visible={state.showAccess} centered width="fitContent"
|
||||||
|
footer={[ <Button type="primary" onClick={() => actions.setShowAccess(false)}>OK</Button> ]}
|
||||||
|
onCancel={() => actions.setShowAccess(false)}>
|
||||||
|
<AccessLayout>
|
||||||
|
<div>{accessLink()}</div>
|
||||||
|
<Button icon={<CopyOutlined />} size="small"
|
||||||
|
onClick={() => onClipboard(accessLink())}
|
||||||
|
/>
|
||||||
|
</AccessLayout>
|
||||||
|
</Modal>
|
||||||
</AccountItemWrapper>
|
</AccountItemWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Button } from 'antd';
|
import { Space, Button } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const AccountItemWrapper = styled.div`
|
export const AccountItemWrapper = styled.div`
|
||||||
@ -74,3 +74,7 @@ export const ResetButton = styled(Button)`
|
|||||||
export const DeleteButton = styled(Button)`
|
export const DeleteButton = styled(Button)`
|
||||||
color: red;
|
color: red;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const AccessLayout = styled(Space)`
|
||||||
|
white-space: nowrap;
|
||||||
|
`
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { useContext, useState, useEffect } from 'react';
|
import { useContext, useState, useEffect } from 'react';
|
||||||
import { getAccountImageUrl } from 'api/getAccountImageUrl';
|
import { getAccountImageUrl } from 'api/getAccountImageUrl';
|
||||||
import { setAccountStatus } from 'api/setAccountStatus';
|
import { setAccountStatus } from 'api/setAccountStatus';
|
||||||
|
import { addAccountAccess } from 'api/addAccountAccess';
|
||||||
|
|
||||||
export function useAccountItem(token, item) {
|
export function useAccountItem(token, item, remove) {
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
statusBusy: false,
|
statusBusy: false,
|
||||||
|
removeBusy: false,
|
||||||
|
showAccess: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateState = (value) => {
|
const updateState = (value) => {
|
||||||
@ -25,6 +28,26 @@ export function useAccountItem(token, item) {
|
|||||||
}, [token, item]);
|
}, [token, item]);
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
|
setAccessLink: async () => {
|
||||||
|
let access = await addAccountAccess(token, item.accountId);
|
||||||
|
updateState({ accessToken: access, showAccess: true });
|
||||||
|
},
|
||||||
|
setShowAccess: (showAccess) => {
|
||||||
|
updateState({ showAccess });
|
||||||
|
},
|
||||||
|
remove: async () => {
|
||||||
|
if (!state.removeBusy) {
|
||||||
|
updateState({ removeBusy: true });
|
||||||
|
try {
|
||||||
|
await remove(state.accountId);
|
||||||
|
}
|
||||||
|
catch(err) {
|
||||||
|
console.log(err);
|
||||||
|
window.alert(err);
|
||||||
|
}
|
||||||
|
updateState({ removeBusy: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
setStatus: async (disabled) => {
|
setStatus: async (disabled) => {
|
||||||
if (!state.statusBusy) {
|
if (!state.statusBusy) {
|
||||||
updateState({ statusBusy: true });
|
updateState({ statusBusy: true });
|
||||||
|
@ -33,7 +33,7 @@ export function Dashboard({ token, config, logout }) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="add">
|
<div class="add">
|
||||||
<Tooltip placement="topRight" title="Add Account">
|
<Tooltip placement="topRight" title="Create Account Link">
|
||||||
<AddButton type="text" size="large" icon={<UserAddOutlined />}></AddButton>
|
<AddButton type="text" size="large" icon={<UserAddOutlined />}></AddButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@ -45,7 +45,8 @@ export function Dashboard({ token, config, logout }) {
|
|||||||
itemLayout="horizontal"
|
itemLayout="horizontal"
|
||||||
dataSource={state.accounts}
|
dataSource={state.accounts}
|
||||||
loading={state.loading}
|
loading={state.loading}
|
||||||
renderItem={item => (<AccountItem token={token} item={item} />)}
|
renderItem={item => (<AccountItem token={token} item={item}
|
||||||
|
remove={actions.removeAccount}/>)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +13,9 @@ export const DashboardWrapper = styled.div`
|
|||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 16px;
|
padding-top: 16px;
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
min-width: 800px;
|
min-width: 800px;
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
@ -25,13 +27,15 @@ export const DashboardWrapper = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
border-bottom: 1px solid #444444;
|
border-bottom: 1px solid #aaaaaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
border-bottom: 1px solid #aaaaaa;
|
||||||
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { setNodeConfig } from 'api/setNodeConfig';
|
import { setNodeConfig } from 'api/setNodeConfig';
|
||||||
import { getNodeAccounts } from 'api/getNodeAccounts';
|
import { getNodeAccounts } from 'api/getNodeAccounts';
|
||||||
|
import { removeAccount } from 'api/removeAccount';
|
||||||
|
|
||||||
export function useDashboard(token, config) {
|
export function useDashboard(token, config) {
|
||||||
|
|
||||||
@ -18,6 +19,10 @@ export function useDashboard(token, config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
|
removeAccount: async (accountId) => {
|
||||||
|
await removeAccount(token, accountId);
|
||||||
|
actions.getAccounts();
|
||||||
|
},
|
||||||
setHost: (value) => {
|
setHost: (value) => {
|
||||||
updateState({ host: value });
|
updateState({ host: value });
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useContext, useState, useEffect } from 'react';
|
import { useContext, useState, useEffect } from 'react';
|
||||||
import { AppContext } from 'context/AppContext';
|
import { AppContext } from 'context/AppContext';
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate, useLocation, useParams } from "react-router-dom";
|
||||||
|
|
||||||
export function useLogin() {
|
export function useLogin() {
|
||||||
|
|
||||||
@ -12,6 +12,7 @@ export function useLogin() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { search } = useLocation();
|
||||||
const app = useContext(AppContext);
|
const app = useContext(AppContext);
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
@ -42,6 +43,16 @@ export function useLogin() {
|
|||||||
actions.updateState({ spinning: false })
|
actions.updateState({ spinning: false })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onAccess: async (token) => {
|
||||||
|
actions.updateState({ spinning: true })
|
||||||
|
try {
|
||||||
|
await app.actions.access(token)
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
window.alert(err);
|
||||||
|
}
|
||||||
|
actions.updateState({ spinning: false })
|
||||||
|
},
|
||||||
onCreate: () => {
|
onCreate: () => {
|
||||||
navigate('/create')
|
navigate('/create')
|
||||||
},
|
},
|
||||||
@ -56,8 +67,12 @@ export function useLogin() {
|
|||||||
if (app.state.access === 'user') {
|
if (app.state.access === 'user') {
|
||||||
navigate('/user')
|
navigate('/user')
|
||||||
}
|
}
|
||||||
if (app.state.access === 'admin') {
|
else {
|
||||||
navigate('/admin')
|
let params = new URLSearchParams(search);
|
||||||
|
let token = params.get("access");
|
||||||
|
if (token) {
|
||||||
|
actions.onAccess(token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (app.actions && app.actions.available) {
|
if (app.actions && app.actions.available) {
|
||||||
|
8
net/web/src/api/addAccountAccess.js
Normal file
8
net/web/src/api/addAccountAccess.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||||
|
|
||||||
|
export async function addAccountAccess(token, accountId) {
|
||||||
|
let access = await fetchWithTimeout(`/admin/accounts/${accountId}/auth?token=${token}`, { method: 'POST' })
|
||||||
|
checkResponse(access);
|
||||||
|
return await access.json()
|
||||||
|
}
|
||||||
|
|
7
net/web/src/api/removeAccount.js
Normal file
7
net/web/src/api/removeAccount.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||||
|
|
||||||
|
export async function removeAccount(token, accountId) {
|
||||||
|
let res = await fetchWithTimeout(`/admin/accounts/${accountId}?token=${token}`, { method: 'DELETE' })
|
||||||
|
checkResponse(res);
|
||||||
|
}
|
||||||
|
|
9
net/web/src/api/setAccountAccess.js
Normal file
9
net/web/src/api/setAccountAccess.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||||
|
|
||||||
|
export async function setAccountAccess(token) {
|
||||||
|
let app = { Name: "indicom", Description: "decentralized communication" }
|
||||||
|
let access = await fetchWithTimeout(`/account/access?token=${token}`, { method: 'PUT', body: JSON.stringify(app) })
|
||||||
|
checkResponse(access)
|
||||||
|
return await access.json()
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
|||||||
import { useEffect, useState, useRef, useContext } from 'react';
|
import { useEffect, useState, useRef, useContext } from 'react';
|
||||||
|
import { useNavigate, useLocation, useParams } from "react-router-dom";
|
||||||
import { getAvailable, getUsername, setLogin, createAccount } from './fetchUtil';
|
import { getAvailable, getUsername, setLogin, createAccount } from './fetchUtil';
|
||||||
|
import { setAccountAccess } from 'api/setAccountAccess';
|
||||||
import { AccountContext } from './AccountContext';
|
import { AccountContext } from './AccountContext';
|
||||||
import { ProfileContext } from './ProfileContext';
|
import { ProfileContext } from './ProfileContext';
|
||||||
import { ArticleContext } from './ArticleContext';
|
import { ArticleContext } from './ArticleContext';
|
||||||
@ -22,6 +24,13 @@ async function appLogin(username, password, updateState, setWebsocket) {
|
|||||||
localStorage.setItem("session", JSON.stringify({ token: access, access: 'user' }));
|
localStorage.setItem("session", JSON.stringify({ token: access, access: 'user' }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function appAccess(token, updateState, setWebsocket) {
|
||||||
|
let access = await setAccountAccess(token)
|
||||||
|
updateState({ token: access, access: 'user' });
|
||||||
|
setWebsocket(access)
|
||||||
|
localStorage.setItem("session", JSON.stringify({ token: access, access: 'user' }));
|
||||||
|
}
|
||||||
|
|
||||||
function appLogout(updateState, clearWebsocket) {
|
function appLogout(updateState, clearWebsocket) {
|
||||||
updateState({ token: null, access: null });
|
updateState({ token: null, access: null });
|
||||||
clearWebsocket()
|
clearWebsocket()
|
||||||
@ -80,6 +89,9 @@ export function useAppContext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const accessActions = {
|
const accessActions = {
|
||||||
|
access: async (token) => {
|
||||||
|
await appAccess(token, updateState, setWebsocket)
|
||||||
|
},
|
||||||
login: async (username, password) => {
|
login: async (username, password) => {
|
||||||
await appLogin(username, password, updateState, setWebsocket)
|
await appLogin(username, password, updateState, setWebsocket)
|
||||||
},
|
},
|
||||||
|
@ -19,10 +19,10 @@ export function useGroupContext() {
|
|||||||
let delta = await getGroups(access.current, revision.current);
|
let delta = await getGroups(access.current, revision.current);
|
||||||
for (let group of delta) {
|
for (let group of delta) {
|
||||||
if (group.data) {
|
if (group.data) {
|
||||||
groups.set(group.id, group);
|
groups.current.set(group.id, group);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
groups.delete(group.id);
|
groups.current.delete(group.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user