delete oauth2 clients

This commit is contained in:
Evert Prants 2022-09-09 17:37:42 +03:00
parent 19f2a820eb
commit 0a90ca6bb9
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
7 changed files with 101 additions and 49 deletions

View File

@ -88,10 +88,12 @@ const EditClientModal = ({
isAdmin: boolean; isAdmin: boolean;
}>) => { }>) => {
const formRef = useRef<HTMLFormElement>(null); const formRef = useRef<HTMLFormElement>(null);
const scopeReq = useSWR<string[]>('/api/admin/oauth2/scopes');
const grantReq = useSWR<string[]>('/api/admin/oauth2/grants');
const { formData, handleInputChange, handleSubmit } = useForm< const { formData, handleInputChange, handleSubmit } = useForm<
Partial<OAuth2ClientListItem> Partial<OAuth2ClientListItem>
>(client || {}, () => { >(client || { grants: 'authorization_code' }, () => {
toast toast
.promise( .promise(
publishJSON( publishJSON(
@ -151,6 +153,16 @@ const EditClientModal = ({
value={formData.scope || ''} value={formData.scope || ''}
onChange={handleInputChange} onChange={handleInputChange}
/> />
{scopeReq.data && (
<span>
<b>Available:</b>{' '}
{scopeReq.data
.filter(
(item) => !formData.scope?.split(' ').includes(item)
)
.join(', ') || 'None'}
</span>
)}
</FormControl> </FormControl>
<FormControl> <FormControl>
<label htmlFor="grants">Allowed grant types</label> <label htmlFor="grants">Allowed grant types</label>
@ -160,6 +172,16 @@ const EditClientModal = ({
value={formData.grants || ''} value={formData.grants || ''}
onChange={handleInputChange} onChange={handleInputChange}
/> />
{grantReq.data && (
<span>
<b>Available:</b>{' '}
{grantReq.data
.filter(
(item) => !formData.grants?.split(' ').includes(item)
)
.join(', ') || 'None'}
</span>
)}
</FormControl> </FormControl>
{isAdmin && ( {isAdmin && (
<> <>
@ -268,7 +290,7 @@ const OAuth2ClientCard = ({
<button <button
onClick={() => onClick={() =>
ModalService.open(EditClientModal, { ModalService.open(EditClientModal, {
client: client, client,
isAdmin, isAdmin,
update, update,
}) })
@ -290,7 +312,32 @@ const OAuth2ClientCard = ({
> >
Copy secret Copy secret
</button> </button>
{!client.activated && <button>Delete client</button>} {!client.activated && (
<button
onClick={() => {
toast
.promise(
publishJSON(
`/api/admin/oauth2/clients/${client.id}`,
'DELETE'
),
{
loading: 'Deleting client...',
success: 'Client deleted!',
error: (err) =>
`Deleting the client failed: ${err.message}`,
}
)
.then((data) => {
if (data) {
update();
}
});
}}
>
Delete client
</button>
)}
</Dropdown> </Dropdown>
</div> </div>
<span className={styles.clientDescription}>{client.description}</span> <span className={styles.clientDescription}>{client.description}</span>

View File

@ -47,7 +47,7 @@ const PrivilegeEditor = ({
user: UserListItem; user: UserListItem;
onChange: (privvy: Privilege[]) => void; onChange: (privvy: Privilege[]) => void;
}) => { }) => {
const [toggle, setToggle] = useState(false); const [userPrivvy, setUserPrivvy] = useState(user.privileges || []);
const [selectionAvailable, setSelectionAvailable] = useState<string[]>([]); const [selectionAvailable, setSelectionAvailable] = useState<string[]>([]);
const [selectionExisting, setSelectionExisting] = useState<string[]>([]); const [selectionExisting, setSelectionExisting] = useState<string[]>([]);
const [availablePrivileges, setAvailablePrivileges] = useState<Privilege[]>( const [availablePrivileges, setAvailablePrivileges] = useState<Privilege[]>(
@ -61,7 +61,7 @@ const PrivilegeEditor = ({
({ id }) => !(user.privileges || []).some((privvy) => privvy.id === id) ({ id }) => !(user.privileges || []).some((privvy) => privvy.id === id)
) )
); );
}, [user, data, toggle]); }, [user, data, userPrivvy]);
return ( return (
<> <>
@ -91,10 +91,9 @@ const PrivilegeEditor = ({
const toAdd = availablePrivileges.filter(({ id }) => const toAdd = availablePrivileges.filter(({ id }) =>
selectionAvailable.includes(id.toString()) selectionAvailable.includes(id.toString())
); );
user.privileges = [...(user.privileges || []), ...toAdd]; setUserPrivvy([...(userPrivvy || []), ...toAdd]);
setToggle(!toggle);
setSelectionAvailable([]); setSelectionAvailable([]);
onChange(user.privileges); onChange(userPrivvy);
}} }}
> >
&gt;&gt; &gt;&gt;
@ -104,12 +103,13 @@ const PrivilegeEditor = ({
disabled={!selectionExisting.length} disabled={!selectionExisting.length}
onClick={() => { onClick={() => {
// Remove privileges // Remove privileges
user.privileges = (user.privileges || []).filter( onChange(userPrivvy);
({ id }) => !selectionExisting.includes(id.toString())
);
onChange(user.privileges);
setSelectionExisting([]); setSelectionExisting([]);
setToggle(!toggle); setUserPrivvy(
(userPrivvy || []).filter(
({ id }) => !selectionExisting.includes(id.toString())
)
);
}} }}
> >
&lt;&lt; &lt;&lt;

View File

@ -7,7 +7,7 @@ export const Button = ({
children, children,
...props ...props
}: { }: {
variant?: 'default' | 'primary' | 'secondary'; variant?: 'default' | 'primary' | 'secondary' | 'link';
onClick: MouseEventHandler<HTMLButtonElement>; onClick: MouseEventHandler<HTMLButtonElement>;
children?: React.ReactNode; children?: React.ReactNode;
} & JSX.IntrinsicElements['button']) => { } & JSX.IntrinsicElements['button']) => {

View File

@ -34,6 +34,12 @@
font-weight: 400; font-weight: 400;
border: 1px solid #a4a4a4; border: 1px solid #a4a4a4;
box-shadow: inset 0 0 4px #0000001f; box-shadow: inset 0 0 4px #0000001f;
+ span {
font-size: 0.875rem;
margin-top: 0.25rem;
color: rgb(100, 100, 100);
}
} }
&:last-child { &:last-child {

View File

@ -21,11 +21,11 @@ const navItems = [
title: 'OAuth2', title: 'OAuth2',
privileges: [['admin', 'admin:oauth2'], 'self:oauth2'], privileges: [['admin', 'admin:oauth2'], 'self:oauth2'],
}, },
{ // {
path: '/privileges', // path: '/privileges',
title: 'Privileges', // title: 'Privileges',
privileges: ['admin', 'admin:user:privilege'], // privileges: ['admin', 'admin:user:privilege'],
}, // },
{ {
path: '/documents', path: '/documents',
title: 'Documents', title: 'Documents',

View File

@ -11,39 +11,37 @@ export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse
) { ) {
if (req.query.code) { if (!req.query.code || !req.query.state) {
if (!req.query.state) { return res.redirect('/');
}
const getAuth = await getAccessToken(req.query.code as string);
const cookies = new Cookies(req, res, { keys: COOKIE_KEYS });
if (getAuth) {
const decrypted = decrypt(req.query.state as string);
const stateToken = cookies.get('validation', { signed: true });
const parsedState = JSON.parse(decrypted);
if (
parsedState.state !== stateToken ||
parsedState.redirect_uri !== redirect
) {
return res.redirect('/'); return res.redirect('/');
} }
const getAuth = await getAccessToken(req.query.code as string); cookies.set('authorization', getAuth.access_token, {
const cookies = new Cookies(req, res, { keys: COOKIE_KEYS }); expires: new Date(Date.now() + getAuth.expires_in * 1000),
secure: process.env.NODE_ENV === 'production',
signed: true,
});
if (getAuth) { cookies.set('validation', undefined, {
const decrypted = decrypt(req.query.state as string); expires: new Date(0),
const stateToken = cookies.get('validation', { signed: true }); secure: process.env.NODE_ENV === 'production',
const parsedState = JSON.parse(decrypted); signed: true,
});
if (
parsedState.state !== stateToken ||
parsedState.redirect_uri !== redirect
) {
return res.redirect('/');
}
cookies.set('authorization', getAuth.access_token, {
expires: new Date(Date.now() + getAuth.expires_in * 1000),
secure: process.env.NODE_ENV === 'production',
signed: true,
});
cookies.set('validation', undefined, {
expires: new Date(0),
secure: process.env.NODE_ENV === 'production',
signed: true,
});
}
res.redirect('/');
} }
res.redirect('/');
} }

View File

@ -2,6 +2,7 @@ input,
button, button,
textarea, textarea,
a { a {
transition: outline 0.15s linear;
&:focus { &:focus {
outline: 4px solid #94cfff9c; outline: 4px solid #94cfff9c;
} }