mirror of
https://github.com/owncast/owncast.git
synced 2024-10-10 19:16:02 +00:00
Merge pull request #12 from owncast/gek/access-tokens
Admin page for managing access tokens
This commit is contained in:
commit
a16e38b057
184
web/pages/access-tokens.tsx
Normal file
184
web/pages/access-tokens.tsx
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Table, Tag, Space, Button, Modal, Checkbox, Input, Typography, Tooltip } from 'antd';
|
||||||
|
import { DeleteOutlined, EyeTwoTone, EyeInvisibleOutlined } from '@ant-design/icons';
|
||||||
|
const { Title, Paragraph, Text } = Typography;
|
||||||
|
|
||||||
|
import format from 'date-fns/format'
|
||||||
|
|
||||||
|
import {
|
||||||
|
fetchData,
|
||||||
|
ACCESS_TOKENS,
|
||||||
|
DELETE_ACCESS_TOKEN,
|
||||||
|
CREATE_ACCESS_TOKEN,
|
||||||
|
} from "../utils/apis";
|
||||||
|
|
||||||
|
const availableScopes = {
|
||||||
|
'CAN_SEND_SYSTEM_MESSAGES': { name: 'System messages', description: 'You can send official messages on behalf of the system', color: 'purple' },
|
||||||
|
'CAN_SEND_MESSAGES': { name: 'User chat messages', description: 'You can send messages on behalf of a username', color: 'green' },
|
||||||
|
};
|
||||||
|
|
||||||
|
function convertScopeStringToTag(scopeString) {
|
||||||
|
if (!scopeString || !availableScopes[scopeString]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scope = availableScopes[scopeString];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip key={scopeString} title={scope.description}>
|
||||||
|
<Tag color={scope.color} >
|
||||||
|
{scope.name}
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NewTokenModal(props) {
|
||||||
|
var selectedScopes = [];
|
||||||
|
|
||||||
|
|
||||||
|
const scopes = Object.keys(availableScopes).map(function (key) {
|
||||||
|
return { value: key, label: availableScopes[key].description }
|
||||||
|
});
|
||||||
|
|
||||||
|
function onChange(checkedValues) {
|
||||||
|
selectedScopes = checkedValues
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveToken() {
|
||||||
|
props.onOk(name, selectedScopes)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal title="Create New Access token" visible={props.visible} onOk={saveToken} onCancel={props.onCancel}>
|
||||||
|
<p><Input value={name} placeholder="Access token name/description" onChange={(input) => setName(input.currentTarget.value)} /></p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Select the permissions this access token will have. It cannot be edited after it's created.
|
||||||
|
</p>
|
||||||
|
<Checkbox.Group options={scopes} onChange={onChange} />
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AccessTokens() {
|
||||||
|
const [tokens, setTokens] = useState([]);
|
||||||
|
const [isTokenModalVisible, setIsTokenModalVisible] = useState(false);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
key: 'delete',
|
||||||
|
render: (text, record) => (
|
||||||
|
<Space size="middle">
|
||||||
|
<Button onClick={() => handleDeleteToken(record.token)} icon={<DeleteOutlined />} />
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Token',
|
||||||
|
dataIndex: 'token',
|
||||||
|
key: 'token',
|
||||||
|
render: (text, record) => (
|
||||||
|
<Input.Password size="small" bordered={false} value={text}/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Scopes',
|
||||||
|
dataIndex: 'scopes',
|
||||||
|
key: 'scopes',
|
||||||
|
render: scopes => (
|
||||||
|
<>
|
||||||
|
{scopes.map(scope => {
|
||||||
|
return convertScopeStringToTag(scope);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Last Used',
|
||||||
|
dataIndex: 'lastUsed',
|
||||||
|
key: 'lastUsed',
|
||||||
|
render: (lastUsed) => {
|
||||||
|
if (!lastUsed) {
|
||||||
|
return 'Never';
|
||||||
|
}
|
||||||
|
const dateObject = new Date(lastUsed);
|
||||||
|
return format(dateObject, 'P p');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getAccessTokens = async () => {
|
||||||
|
try {
|
||||||
|
const result = await fetchData(ACCESS_TOKENS);
|
||||||
|
setTokens(result);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getAccessTokens();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function handleDeleteToken(token) {
|
||||||
|
try {
|
||||||
|
const result = await fetchData(DELETE_ACCESS_TOKEN, { method: 'POST', data: { token: token } });
|
||||||
|
getAccessTokens();
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSaveToken(name: string, scopes: string[]) {
|
||||||
|
try {
|
||||||
|
const newToken = await fetchData(CREATE_ACCESS_TOKEN, { method: 'POST', data: { name: name, scopes: scopes } });
|
||||||
|
setTokens(tokens.concat(newToken));
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError(error) {
|
||||||
|
console.error("error", error);
|
||||||
|
alert(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const showCreateTokenModal = () => {
|
||||||
|
setIsTokenModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTokenModalSaveButton = (name, scopes) => {
|
||||||
|
setIsTokenModalVisible(false);
|
||||||
|
handleSaveToken(name, scopes);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTokenModalCancel = () => {
|
||||||
|
setIsTokenModalVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Title>Access Tokens</Title>
|
||||||
|
<Paragraph>
|
||||||
|
Access tokens are used to allow external, 3rd party tools to perform specific actions on your Owncast server.
|
||||||
|
They should be kept secure and never included in client code, instead they should be kept on a server that you control.
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph>
|
||||||
|
Read more about how to use these tokens at _some documentation here_.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Table rowKey="token" columns={columns} dataSource={tokens} pagination={false} />
|
||||||
|
<Button onClick={showCreateTokenModal}>Create Access Token</Button>
|
||||||
|
<NewTokenModal visible={isTokenModalVisible} onOk={handleTokenModalSaveButton} onCancel={handleTokenModalCancel} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -40,6 +40,15 @@ export const CHAT_HISTORY = `${API_LOCATION}chat/messages`;
|
|||||||
// Get chat history
|
// Get chat history
|
||||||
export const UPDATE_CHAT_MESSGAE_VIZ = `${NEXT_PUBLIC_API_HOST}api/admin/chat/updatemessagevisibility`;
|
export const UPDATE_CHAT_MESSGAE_VIZ = `${NEXT_PUBLIC_API_HOST}api/admin/chat/updatemessagevisibility`;
|
||||||
|
|
||||||
|
// Get all access tokens
|
||||||
|
export const ACCESS_TOKENS = `${API_LOCATION}accesstokens`;
|
||||||
|
|
||||||
|
// Delete a single access token
|
||||||
|
export const DELETE_ACCESS_TOKEN = `${API_LOCATION}deleteaccesstoken`;
|
||||||
|
|
||||||
|
// Create a new access token
|
||||||
|
export const CREATE_ACCESS_TOKEN = `${API_LOCATION}createaccesstoken`;
|
||||||
|
|
||||||
// Get webhooks
|
// Get webhooks
|
||||||
export const WEBHOOKS = `${API_LOCATION}webhooks`;
|
export const WEBHOOKS = `${API_LOCATION}webhooks`;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user