pl-api: Support GoToSocial tokens page
Signed-off-by: mkljczk <git@mkljczk.pl>
This commit is contained in:
@ -1260,11 +1260,11 @@ class PlApiClient {
|
||||
* Requires features{@link Features['sessions']}.
|
||||
* @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apioauth_tokens}
|
||||
*/
|
||||
getOauthTokens: async () => {
|
||||
const response = await this.request('/api/oauth_tokens');
|
||||
|
||||
return v.parse(filteredArray(oauthTokenSchema), response.json);
|
||||
},
|
||||
getOauthTokens: () => this.#paginatedGet(
|
||||
this.features.version.software === GOTOSOCIAL ? '/api/v1/tokens' : '/api/oauth_tokens',
|
||||
{},
|
||||
oauthTokenSchema,
|
||||
),
|
||||
|
||||
/**
|
||||
* Revoke a user session by its ID
|
||||
@ -1272,8 +1272,17 @@ class PlApiClient {
|
||||
* Requires features{@link Features['sessions']}.
|
||||
* @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#delete-apioauth_tokensid}
|
||||
*/
|
||||
deleteOauthToken: async (oauthTokenId: number) => {
|
||||
const response = await this.request(`/api/oauth_tokens/${oauthTokenId}`, { method: 'DELETE' });
|
||||
deleteOauthToken: async (oauthTokenId: string) => {
|
||||
let response;
|
||||
|
||||
switch (this.features.version.software) {
|
||||
case GOTOSOCIAL:
|
||||
response = await this.request(`/api/v1/tokens/${oauthTokenId}/invalidate`, { method: 'POST' });
|
||||
break;
|
||||
default:
|
||||
response = await this.request(`/api/oauth_tokens/${oauthTokenId}`, { method: 'DELETE' });
|
||||
break;
|
||||
}
|
||||
|
||||
return response.json as {};
|
||||
},
|
||||
|
||||
@ -8,14 +8,29 @@ import { datetimeSchema } from './utils';
|
||||
*/
|
||||
const oauthTokenSchema = v.pipe(
|
||||
v.any(),
|
||||
v.transform((token: any) => ({
|
||||
...token,
|
||||
valid_until: token?.valid_until?.padEnd(27, 'Z'),
|
||||
})),
|
||||
v.transform((token: any) => {
|
||||
if (token.application) {
|
||||
return {
|
||||
...token,
|
||||
app_name: token.application.name,
|
||||
app_website: token.application.website,
|
||||
scopes: token.scope.split(' '),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...token,
|
||||
valid_until: token?.valid_until?.padEnd(27, 'Z'),
|
||||
};
|
||||
}),
|
||||
v.object({
|
||||
app_name: v.string(),
|
||||
id: v.number(),
|
||||
valid_until: datetimeSchema,
|
||||
app_website: v.fallback(v.string(), ''),
|
||||
id: v.pipe(v.unknown(), v.transform(String)),
|
||||
created_at: v.fallback(v.nullable(datetimeSchema), null),
|
||||
valid_until: v.fallback(v.nullable(datetimeSchema), null),
|
||||
last_used: v.fallback(v.nullable(datetimeSchema), null),
|
||||
scopes: v.fallback(v.array(v.string()), []),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@ -1267,8 +1267,14 @@ const getFeatures = (instance: Instance) => {
|
||||
* Ability to manage account sessions.
|
||||
* @see GET /api/oauth_tokens.json
|
||||
* @see DELETE /api/oauth_tokens/:id
|
||||
* @see GET /api/v1/tokens
|
||||
* @see GET /api/v1/tokens/:id
|
||||
* @see POST /api/v1/tokens/:id/invalidate
|
||||
*/
|
||||
sessions: v.software === PLEROMA,
|
||||
sessions: any([
|
||||
v.software === PLEROMA,
|
||||
v.software === GOTOSOCIAL && gte(v.version, '0.18.2'),
|
||||
]),
|
||||
|
||||
/**
|
||||
* Can store client settings in the database.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pl-api",
|
||||
"version": "1.0.0-rc.29",
|
||||
"version": "1.0.0-rc.30",
|
||||
"type": "module",
|
||||
"homepage": "https://github.com/mkljczk/pl-fe/tree/develop/packages/pl-api",
|
||||
"repository": {
|
||||
|
||||
@ -103,7 +103,7 @@
|
||||
"multiselect-react-dropdown": "^2.0.25",
|
||||
"mutative": "^1.1.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"pl-api": "^1.0.0-rc.29",
|
||||
"pl-api": "^1.0.0-rc.30",
|
||||
"postcss": "^8.4.49",
|
||||
"process": "^0.11.10",
|
||||
"punycode": "^2.1.1",
|
||||
|
||||
@ -21,11 +21,11 @@ const REVOKE_TOKEN_SUCCESS = 'REVOKE_TOKEN_SUCCESS' as const;
|
||||
|
||||
const fetchOAuthTokens = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
getClient(getState).settings.getOauthTokens().then((tokens) => {
|
||||
getClient(getState).settings.getOauthTokens().then(({ items: tokens }) => {
|
||||
dispatch<SecurityAction>({ type: FETCH_TOKENS_SUCCESS, tokens });
|
||||
});
|
||||
|
||||
const revokeOAuthTokenById = (tokenId: number) =>
|
||||
const revokeOAuthTokenById = (tokenId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
getClient(getState).settings.deleteOauthToken(tokenId).then(() => {
|
||||
dispatch<SecurityAction>({ type: REVOKE_TOKEN_SUCCESS, tokenId });
|
||||
@ -65,7 +65,7 @@ const moveAccount = (targetAccount: string, password: string) =>
|
||||
|
||||
type SecurityAction =
|
||||
| { type: typeof FETCH_TOKENS_SUCCESS; tokens: Array<OauthToken> }
|
||||
| { type: typeof REVOKE_TOKEN_SUCCESS; tokenId: number }
|
||||
| { type: typeof REVOKE_TOKEN_SUCCESS; tokenId: string }
|
||||
| { type: typeof AUTH_LOGGED_OUT; account: Account }
|
||||
|
||||
export {
|
||||
|
||||
@ -4,7 +4,7 @@ import React, { useMemo } from 'react';
|
||||
import { hexToHsl } from 'pl-fe/utils/theme';
|
||||
|
||||
interface IBadge {
|
||||
title: React.ReactNode;
|
||||
title: React.ReactNode | string;
|
||||
slug: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { defineMessages, FormattedDate, useIntl } from 'react-intl';
|
||||
import { defineMessages, FormattedDate, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchOAuthTokens, revokeOAuthTokenById } from 'pl-fe/actions/security';
|
||||
import Badge from 'pl-fe/components/badge';
|
||||
import Button from 'pl-fe/components/ui/button';
|
||||
import Card, { CardBody, CardHeader, CardTitle } from 'pl-fe/components/ui/card';
|
||||
import Column from 'pl-fe/components/ui/column';
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
import Icon from 'pl-fe/components/ui/icon';
|
||||
import Spinner from 'pl-fe/components/ui/spinner';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
@ -51,19 +53,82 @@ const AuthToken: React.FC<IAuthToken> = ({ token, isCurrent }) => {
|
||||
|
||||
return (
|
||||
<div className='rounded-lg bg-gray-100 p-4 dark:bg-primary-800'>
|
||||
<Stack space={2}>
|
||||
<Stack>
|
||||
<Text size='md' weight='medium'>{token.app_name}</Text>
|
||||
<Stack space={2} className='h-full justify-between'>
|
||||
<Stack space={1}>
|
||||
<Text size='md' weight='medium'>
|
||||
<HStack space={1} alignItems='center'>
|
||||
{token.app_name}
|
||||
{token.app_website && (
|
||||
<a href={token.app_website} target='_blank' rel='noopener'>
|
||||
<Icon
|
||||
src={require('@tabler/icons/outline/external-link.svg')}
|
||||
className='inline size-4 text-inherit'
|
||||
/>
|
||||
</a>
|
||||
)}
|
||||
</HStack>
|
||||
</Text>
|
||||
{token.scopes?.length > 0 && (
|
||||
<HStack space={2} alignItems='center' wrap>
|
||||
<Text size='sm' theme='muted'>
|
||||
<FormattedMessage
|
||||
id='security.tokens.scopes'
|
||||
defaultMessage='Scopes:'
|
||||
/>
|
||||
</Text>
|
||||
{token.scopes.map((scope, index) => (
|
||||
<Badge title={scope} slug='opaque' key={scope} />
|
||||
))}
|
||||
</HStack>
|
||||
)}
|
||||
{token.created_at && (
|
||||
<Text size='sm' theme='muted'>
|
||||
<FormattedMessage
|
||||
id='security.tokens.created_at'
|
||||
defaultMessage='Created on {date}'
|
||||
values={{ date: <FormattedDate
|
||||
value={token.created_at}
|
||||
hour12
|
||||
year='numeric'
|
||||
month='short'
|
||||
day='2-digit'
|
||||
hour='numeric'
|
||||
minute='2-digit'
|
||||
/> }}
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
{token.last_used && (
|
||||
<Text size='sm' theme='muted'>
|
||||
<FormattedMessage
|
||||
id='security.tokens.last_used'
|
||||
defaultMessage='Last used on {date}'
|
||||
values={{ date: <FormattedDate
|
||||
value={token.last_used}
|
||||
hour12
|
||||
year='numeric'
|
||||
month='short'
|
||||
day='2-digit'
|
||||
hour='numeric'
|
||||
minute='2-digit'
|
||||
/> }}
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
{token.valid_until && (
|
||||
<Text size='sm' theme='muted'>
|
||||
<FormattedDate
|
||||
value={token.valid_until}
|
||||
hour12
|
||||
year='numeric'
|
||||
month='short'
|
||||
day='2-digit'
|
||||
hour='numeric'
|
||||
minute='2-digit'
|
||||
<FormattedMessage
|
||||
id='security.tokens.valid_until'
|
||||
defaultMessage='Expires on {date}'
|
||||
values={{ date: <FormattedDate
|
||||
value={token.valid_until}
|
||||
hour12
|
||||
year='numeric'
|
||||
month='short'
|
||||
day='2-digit'
|
||||
hour='numeric'
|
||||
minute='2-digit'
|
||||
/> }}
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@ -26,7 +26,7 @@ const initialState: State = {
|
||||
},
|
||||
};
|
||||
|
||||
const deleteToken = (state: State, tokenId: number) => state.tokens = state.tokens.filter(token => token.id !== tokenId);
|
||||
const deleteToken = (state: State, tokenId: string) => state.tokens = state.tokens.filter(token => token.id !== tokenId);
|
||||
|
||||
const importMfa = (state: State, data: any) => state.mfa = data;
|
||||
|
||||
|
||||
@ -7553,10 +7553,10 @@ pkg-dir@^4.1.0:
|
||||
dependencies:
|
||||
find-up "^4.0.0"
|
||||
|
||||
pl-api@^1.0.0-rc.29:
|
||||
version "1.0.0-rc.29"
|
||||
resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-1.0.0-rc.29.tgz#3d49281b15b1fca70cc631bc3107ca27f68c6034"
|
||||
integrity sha512-cgQUX14DyaoSpPwU1eBB+VkABCd5YwHqFisJ0erVqYJSbKG17oeyrFiu/BCer7/ExQR8GF0UyoZvcbkCsQv1eQ==
|
||||
pl-api@^1.0.0-rc.30:
|
||||
version "1.0.0-rc.30"
|
||||
resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-1.0.0-rc.30.tgz#1702557d5e723ba40c73323782c4ad3910e3f908"
|
||||
integrity sha512-9rmx87EV3oqI3e2ZonHVRcYBS3J1SyfQBNMmWwPmr0aPiA/kVeDcCgRjlxc0Z0srLWGg4Bwfxdcp8zx7aSRMqg==
|
||||
dependencies:
|
||||
blurhash "^2.0.5"
|
||||
http-link-header "^1.1.3"
|
||||
|
||||
Reference in New Issue
Block a user