0

Merge pull request #12 from owncast/gek/access-tokens

Admin page for managing access tokens
This commit is contained in:
Gabe Kangas 2021-01-06 21:38:06 -08:00 committed by GitHub
commit a16e38b057
2 changed files with 193 additions and 0 deletions

184
web/pages/access-tokens.tsx Normal file
View 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>
);
}

View File

@ -40,6 +40,15 @@ export const CHAT_HISTORY = `${API_LOCATION}chat/messages`;
// Get chat history
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
export const WEBHOOKS = `${API_LOCATION}webhooks`;