From 83c235e94defd17bed6a6cdbd05ad82dff782d06 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Wed, 14 Sep 2022 20:31:38 +0300 Subject: [PATCH] support multiple redirect uris --- components/AuditPage/AuditPage.tsx | 4 + components/LoginPage/LoginPage.tsx | 22 ++- components/OAuth2Page/OAuth2Page.module.scss | 13 ++ components/OAuth2Page/OAuth2Page.tsx | 178 ++++++++++++++----- components/UsersPage/UsersPage.tsx | 16 +- components/common/Button/Button.module.scss | 1 + components/common/Form/Form.module.scss | 11 +- lib/hooks/useForm.ts | 19 +- pages/index.tsx | 4 + 9 files changed, 201 insertions(+), 67 deletions(-) diff --git a/components/AuditPage/AuditPage.tsx b/components/AuditPage/AuditPage.tsx index 3d8c10b..56f738f 100644 --- a/components/AuditPage/AuditPage.tsx +++ b/components/AuditPage/AuditPage.tsx @@ -1,3 +1,4 @@ +import Head from 'next/head'; import { useEffect, useState } from 'react'; import useSWR from 'swr'; import useUser from '../../lib/hooks/useUser'; @@ -168,6 +169,9 @@ export const AuditPage = () => { return ( <> + + Audit logs | Icy Network Administration +

Audit logs

diff --git a/components/LoginPage/LoginPage.tsx b/components/LoginPage/LoginPage.tsx index 808062b..81de291 100644 --- a/components/LoginPage/LoginPage.tsx +++ b/components/LoginPage/LoginPage.tsx @@ -1,15 +1,21 @@ +import Head from 'next/head'; import Link from 'next/link'; import styles from './LoginPage.module.scss'; export const LoginPage = () => { return ( -
-
-

Icy Network Administration

- - Log in with Icy Network - -
-
+ <> + + Login | Icy Network Administration + +
+
+

Icy Network Administration

+ + Log in with Icy Network + +
+
+ ); }; diff --git a/components/OAuth2Page/OAuth2Page.module.scss b/components/OAuth2Page/OAuth2Page.module.scss index def59ab..2e4afa9 100644 --- a/components/OAuth2Page/OAuth2Page.module.scss +++ b/components/OAuth2Page/OAuth2Page.module.scss @@ -52,3 +52,16 @@ flex-direction: column; gap: 1rem; } + +.urlWrapper { + display: flex; + gap: 0.5rem; + + :first-child { + flex-grow: 1; + } + + button { + min-width: 4.625rem; + } +} diff --git a/components/OAuth2Page/OAuth2Page.tsx b/components/OAuth2Page/OAuth2Page.tsx index 4ac2989..65233e5 100644 --- a/components/OAuth2Page/OAuth2Page.tsx +++ b/components/OAuth2Page/OAuth2Page.tsx @@ -1,4 +1,4 @@ -import useSWR, { mutate } from 'swr'; +import useSWR from 'swr'; import Image from 'next/image'; import { OAuth2ClientListItem, @@ -10,7 +10,7 @@ import { Paginator } from '../common/Paginator/Paginator'; import styles from './OAuth2Page.module.scss'; import { UPLOADS_URL } from '../../lib/constants'; import application from '../../public/application.png'; -import { ChangeEvent, useMemo, useRef, useState } from 'react'; +import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react'; import useUser from '../../lib/hooks/useUser'; import { Container } from '../common/Container/Container'; import { Header } from '../common/Header/Header'; @@ -28,51 +28,141 @@ import toast from 'react-hot-toast'; import { Button } from '../common/Button/Button'; import userHasPrivileges from '../../lib/utils/has-privileges'; import { publishJSON } from '../../lib/utils/fetch'; +import Head from 'next/head'; -const LINK_NAMES = { +const LINK_NAMES: Record = { redirect_uri: 'Redirect URI', terms: 'Terms of Service', privacy: 'Privacy Policy', website: 'Website', }; +const REDIRECT_URI_COUNT = 3; + const LinkEdit = ({ + link, + onChange, + onRemove, +}: { + link: Partial; + onChange: () => void; + onRemove: () => void; +}) => { + return ( + + +
+ { + link.url = e.target.value; + onChange(); + }} + /> + +
+ {link.type === OAuth2ClientURLType.REDIRECT_URI && ( + Wildcards are NOT allowed! + )} +
+ ); +}; + +const LinkEditor = ({ formData, handleInputChange, - linkType, }: { formData: Partial; handleInputChange: ( - e: ChangeEvent, + e?: ChangeEvent, setValue?: any, formField?: string ) => void; - linkType: OAuth2ClientURLType; }) => { - const formUrl = useMemo>( - () => (formData.urls || []).find(({ type }) => type === linkType) || {}, - [formData, linkType] + const [links, setLinks] = useState[]>( + formData.urls || [] ); + const [addNewSelection, setAddNewSelection] = useState(); + const availableTypes = useMemo( + () => + Object.values(OAuth2ClientURLType).filter((type) => + type === 'redirect_uri' + ? links.filter( + (link) => link.type === OAuth2ClientURLType.REDIRECT_URI + ).length < REDIRECT_URI_COUNT + : !links.some((link) => link.type === type) + ), + [links] + ); + + useEffect(() => { + if ( + (!addNewSelection || + availableTypes.indexOf(addNewSelection as OAuth2ClientURLType) === + -1) && + availableTypes.length + ) { + setAddNewSelection(availableTypes[0]); + } + }, [availableTypes, addNewSelection]); + return ( - - - { - if (!formUrl.type) { - formUrl.type = linkType; - (formData.urls as Partial[]) = [ - ...(formData.urls || []), - formUrl, - ]; - } - formUrl.url = e.target.value; - handleInputChange(e, formData.urls, 'urls'); - }} - /> - + <> +

Client URLs

+ {links.map((link, index) => ( + { + setLinks(links); + handleInputChange(undefined, links, 'urls'); + }} + onRemove={() => { + const clone = links.slice(); + clone.splice(index, 1); + setLinks(clone); + handleInputChange(undefined, clone, 'urls'); + }} + /> + ))} + + {availableTypes.length > 0 && ( +
+ + +
+ )} +
+ ); }; @@ -207,26 +297,10 @@ const EditClientModal = ({ )} - - - - + > @@ -392,6 +466,7 @@ const OAuth2ClientCard = ({ const OAuth2ClientList = ({ isAdmin }: { isAdmin: boolean }) => { const [pageIndex, setPageIndex] = useState(1); + const [clientCount, setClientCount] = useState(); const [searchTerm, setSearchTerm] = useState(''); const { data, mutate } = useSWR>( `/api/admin/oauth2/clients?page=${pageIndex}${ @@ -399,10 +474,16 @@ const OAuth2ClientList = ({ isAdmin }: { isAdmin: boolean }) => { }&pageSize=8` ); + useEffect(() => { + if (data?.pagination?.rowCount) { + setClientCount(data?.pagination.rowCount); + } + }, [data]); + return ( <>
-

OAuth2 clients

+

OAuth2 clients{clientCount && ` (${clientCount})`}