From bf4c61f73243c874027d41c71998b88ea7c1d37a Mon Sep 17 00:00:00 2001 From: Viet Nguyen <3805254+vnugent@users.noreply.github.com> Date: Mon, 11 Nov 2024 09:10:40 -0800 Subject: [PATCH 1/2] feat: add mobile login handler (#1207) * feat: add mobile login handler * chore: fix user mgmt code due to auth0 lib upgrade * chore: remove dead code --- .env | 2 +- package.json | 2 +- src/Config.ts | 2 + src/app/api/mobile/login/route.ts | 40 ++ src/app/api/mobile/refreshToken/route.ts | 37 ++ src/components/area/panel/panelList.tsx | 79 ---- src/components/area/panel/sidePanel.tsx | 86 ---- src/components/basecamp/UserForm.tsx | 10 +- src/components/basecamp/Users.tsx | 12 +- src/components/users/FavouriteButton.tsx | 110 ----- src/js/auth/CurrentUserClient.ts | 26 -- src/js/auth/ManagementClient.ts | 102 +---- src/js/auth/mobile.ts | 22 + src/js/auth/withMobileAuth.ts | 22 + src/pages/api/basecamp/migrate.ts | 56 --- src/pages/api/basecamp/userRoles.ts | 2 +- src/pages/api/user/exists.ts | 28 -- src/pages/api/user/fav.ts | 156 ------- src/pages/api/user/metadataClient.ts | 137 ------ src/pages/api/user/ticks.ts | 92 ++-- src/pages/p/[...slug].tsx | 124 ------ yarn.lock | 528 +---------------------- 22 files changed, 227 insertions(+), 1448 deletions(-) create mode 100644 src/app/api/mobile/login/route.ts create mode 100644 src/app/api/mobile/refreshToken/route.ts delete mode 100644 src/components/area/panel/panelList.tsx delete mode 100644 src/components/area/panel/sidePanel.tsx delete mode 100644 src/components/users/FavouriteButton.tsx delete mode 100644 src/js/auth/CurrentUserClient.ts create mode 100644 src/js/auth/mobile.ts create mode 100644 src/js/auth/withMobileAuth.ts delete mode 100644 src/pages/api/basecamp/migrate.ts delete mode 100644 src/pages/api/user/exists.ts delete mode 100644 src/pages/api/user/fav.ts delete mode 100644 src/pages/api/user/metadataClient.ts delete mode 100644 src/pages/p/[...slug].tsx diff --git a/.env b/.env index 39d0dd7f0..bf901343a 100644 --- a/.env +++ b/.env @@ -9,7 +9,7 @@ AUTH0_CLIENT_SECRET=send request to hello at openbeta.io NEXTAUTH_SECRET=vQyFR7gskaqxehN0cI/53r+duWc5Et0ktdoz6KozTCo= # Auth0 Management API -AUTH0_MGMT_CLIENT_ID=seD3dNxnZ4jXik1dzdQEgPSNSvXGBsqA +AUTH0_MGMT_CLIENT_ID=Ecyj4oke3Cpk1khRsdKr8njen6ZZKePF AUTH0_MGMT_CLIENT_SECRET=send request to hello at openbeta.io ######### Client-side vars ############ diff --git a/package.json b/package.json index 751709256..307c0952c 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@turf/line-to-polygon": "^6.5.0", "@udecode/zustood": "^1.1.3", "@vercel/edge": "^1.1.1", - "auth0": "^2.42.0", + "auth0": "^4.12.0", "awesome-debounce-promise": "^2.1.0", "axios": "^0.24.0", "classnames": "^2.3.1", diff --git a/src/Config.ts b/src/Config.ts index 5e3de6a36..663be347c 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -14,6 +14,7 @@ interface AUTH_CONFIG_SERVER_TYPE { clientSecret: string mgmtClientId: string mgmtClientSecret: string + mgmtClientAudience: string nextauthSecret: string } @@ -26,6 +27,7 @@ if (typeof window === 'undefined') { clientSecret: checkAndPrintWarning('AUTH0_CLIENT_SECRET', process.env.AUTH0_CLIENT_SECRET), mgmtClientId: checkAndPrintWarning('AUTH0_MGMT_CLIENT_ID', process.env.AUTH0_MGMT_CLIENT_ID), mgmtClientSecret: checkAndPrintWarning('AUTH0_MGMT_CLIENT_SECRET', process.env.AUTH0_MGMT_CLIENT_SECRET), + mgmtClientAudience: checkAndPrintWarning('AUTH0_MGMT_CLIENT_AUDIENCE', process.env.AUTH0_MGMT_CLIENT_AUDIENCE), nextauthSecret: checkAndPrintWarning('NEXTAUTH_SECRET', process.env.NEXTAUTH_SECRET) } } else { diff --git a/src/app/api/mobile/login/route.ts b/src/app/api/mobile/login/route.ts new file mode 100644 index 000000000..14b408ed6 --- /dev/null +++ b/src/app/api/mobile/login/route.ts @@ -0,0 +1,40 @@ +import { NextRequest, NextResponse } from 'next/server' +import * as Auth0 from 'auth0' +import { auth0Client, isNullOrEmpty } from '@/js/auth/mobile' +import { withMobileAuth } from '@/js/auth/withMobileAuth' + +/** + * Mobile login handler + */ +async function postHandler (request: NextRequest): Promise { + let username: string, password: string + try { + const data = await request.json() + username = data.username + password = data.password + + if (isNullOrEmpty(username) || isNullOrEmpty(password)) { + console.error('Empty username/password!') + throw new Error('Invalid payload') + } + } catch (error) { + return NextResponse.json({ error: 'Unexpected error', status: 400 }) + } + + let response: Auth0.JSONApiResponse | undefined + try { + response = await auth0Client.oauth.passwordGrant({ + username, + password, + scope: 'openid profile email offline_access', + audience: 'https://api.openbeta.io' + }) + + return NextResponse.json({ data: response.data }) + } catch (error) { + console.error('#### Auth0 error ####', error) + return NextResponse.json({ error: 'Unexpected auth error', status: 403 }) + } +} + +export const POST = withMobileAuth(postHandler) diff --git a/src/app/api/mobile/refreshToken/route.ts b/src/app/api/mobile/refreshToken/route.ts new file mode 100644 index 000000000..898fd9101 --- /dev/null +++ b/src/app/api/mobile/refreshToken/route.ts @@ -0,0 +1,37 @@ +import { NextRequest, NextResponse } from 'next/server' +import * as Auth0 from 'auth0' +import { auth0Client, isNullOrEmpty } from '@/js/auth/mobile' +import { withMobileAuth } from '@/js/auth/withMobileAuth' + +/** + * Mobile refresh token handler + */ +async function postHandler (request: NextRequest): Promise { + let refreshToken: string + try { + const data = await request.json() + refreshToken = data.refreshToken + + if (isNullOrEmpty(refreshToken)) { + console.error('Empty refreshToken!') + throw new Error('Invalid payload') + } + } catch (error) { + return NextResponse.json({ error: 'Unexpected error', status: 400 }) + } + + let response: Auth0.JSONApiResponse | undefined + try { + response = await auth0Client.oauth.refreshTokenGrant({ + refresh_token: refreshToken, + audience: 'https://api.openbeta.io' + }) + + return NextResponse.json({ data: response.data }) + } catch (error) { + console.error('#### Auth0 error ####', error) + return NextResponse.json({ error: 'Unexpected auth error', status: 403 }) + } +} + +export const POST = withMobileAuth(postHandler) diff --git a/src/components/area/panel/panelList.tsx b/src/components/area/panel/panelList.tsx deleted file mode 100644 index 60e37c229..000000000 --- a/src/components/area/panel/panelList.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import * as React from 'react' -import { useEffect, useState } from 'react' -import { APIFavouriteCollections } from '../../../pages/api/user/fav' -import ListItem, { ListItemEntity } from './listItem' - -interface PanelListProps { - onFocus: (id: string | null) => void - onSelect: (id: string | null) => void - - items: ListItemEntity[] - selected: string | null - focused: string | null -} - -export default function PanelList (props: PanelListProps): JSX.Element { - const [favs, setFavs] = useState([]) - useEffect(() => { - fetch('/api/user/fav') - .then(async res => await res.json()) - .then((collections: APIFavouriteCollections) => { - const favourites = collections.areaCollections.favourites - if (favourites === undefined) { - setFavs([]) - return - } - - setFavs(favourites) - }) - .catch(console.error) - }, []) - - useEffect(() => { - if (props.selected !== null) { - const selectedElement = document.getElementById(`${props.selected}-xqoops98`) - if (selectedElement === null) { - return - } - selectedElement.scrollIntoView({ behavior: 'smooth', block: 'center' }) - } - }, [props.selected]) - - function reFocusCheck (newId: string | null): void { - if (props.focused !== newId) { - props.onFocus(newId) - } else { - props.onFocus(null) - } - } - - function reClickCheck (newId: string | null): void { - if (props.selected !== newId) { - props.onSelect(newId) - } else { - props.onSelect(null) - } - } - - return ( -
props.onFocus(null)} - > - {props.items.map(item => ( -
- -
- ))} -
- ) -} diff --git a/src/components/area/panel/sidePanel.tsx b/src/components/area/panel/sidePanel.tsx deleted file mode 100644 index aa106cd8c..000000000 --- a/src/components/area/panel/sidePanel.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from 'react' -import { useRef } from 'react' -import { ListItemEntity } from './listItem' -import { PanelHeader, PanelHeaderProps } from './panelHeader' -import PanelList from './panelList' -import PanelOverview from './panelOverview' - -interface SidePanelProps extends PanelHeaderProps { - /** - * If an item is focused (not clicked / selected) this must reflect the ID - * of that entity (so that the feedback to the user is consistent). - * null indicates no element is focused, it is not an invitation to treat as optional - */ - focused: string | null - /** If an item is selected, the user has asserted their intention to recieve more - * information on the selected entity (Whatever it may be) so children must change - * their visual state accordingly. - * null indicates no element is selected, it is not an invitation to treat as optional - */ - selected: string | null - - /** - * This is what the list expects in order to display and interact with the entities - * you wish to list here. - */ - items: ListItemEntity[] - - /** - * Focusing and selecting are distinct, but both require the parent component to - * store this state. - */ - onFocus: (id: string | null) => void - - /** - * Focusing and selecting are distinct, but both require the parent component to - * store this state. - */ - onSelect: (id: string | null) => void -} - -/** In the view spec for viewing and exploring conforming geo-entities - * there is a left side panel containing the expounded data that is shown - * on the expandable map on the right. This will be a recognizable map usage - * for anyone that has seen google maps. - */ -export default function SidePanel (props: SidePanelProps): JSX.Element { - const heroRef = useRef(null) - let allowableListHeight = 400 - if (heroRef.current !== undefined && heroRef.current !== null) { - allowableListHeight = heroRef.current.clientHeight - } - - return ( -
-
- - - -
- -
- -
-
- ) -} diff --git a/src/components/basecamp/UserForm.tsx b/src/components/basecamp/UserForm.tsx index da15036dd..7797065c5 100644 --- a/src/components/basecamp/UserForm.tsx +++ b/src/components/basecamp/UserForm.tsx @@ -1,17 +1,17 @@ -import type { User } from 'auth0' +import type { UserProfile } from 'auth0' import clx from 'classnames' import { TextArea } from '../ui/form' import { useForm, FormProvider } from 'react-hook-form' import { MUUID_VALIDATION, conjoinedStringToArray } from './utils' import axios from 'axios' import useSWR from 'swr' -import { Role } from 'auth0' +import { UserInfoResponse } from 'auth0' import { UserRole } from '../../js/types' import MultiSelect from '../ui/form/MultiSelect' import { useEffect } from 'react' interface UserFormProps { - user: User + user: UserProfile onClose: () => void } @@ -26,11 +26,11 @@ const fetcher = async (url: string): Promise => (await axios.get(url)).data * Form for updating users. */ export default function UserForm ({ user, onClose }: UserFormProps): JSX.Element { - const userId = user.user_id + const userId = user.user_id as string if (userId == null) { return
Can't load user. Missing user_id.
} - const { data: roles, error, mutate } = useSWR(`/api/basecamp/userRoles?userId=${userId}`, fetcher) + const { data: roles, error, mutate } = useSWR(`/api/basecamp/userRoles?userId=${userId}`, fetcher) if (error != null) { return
{error}
} diff --git a/src/components/basecamp/Users.tsx b/src/components/basecamp/Users.tsx index c65b4b2d2..b437d8ad7 100644 --- a/src/components/basecamp/Users.tsx +++ b/src/components/basecamp/Users.tsx @@ -4,7 +4,7 @@ import { formatDistanceToNow, parseISO } from 'date-fns' import { useSession, signIn } from 'next-auth/react' import useSWR from 'swr' import axios from 'axios' -import { UserPage } from 'auth0' +import { GetUsers200ResponseOneOf, GetUsers200ResponseOneOfInner } from 'auth0' import { MagnifyingGlassIcon, PencilSquareIcon } from '@heroicons/react/20/solid' import { IUserMetadata, UserRole } from '../../js/types/User' @@ -12,7 +12,7 @@ import { usersToCsv, saveAsCSVFile } from '../../js/utils/csv' import { CLIENT_CONFIG } from '../../js/configs/clientConfig' import { Input } from '../ui/form' import { useForm, FormProvider } from 'react-hook-form' -import type { User } from 'auth0' +import type { UserProfile } from 'auth0' import CreateUpdateModal from './CreateUpdateModal' import UserForm from './UserForm' import { RulesType } from '../../js/types' @@ -55,7 +55,7 @@ const UserTable = (): JSX.Element => { const [currentPage, setPage] = useState(0) const [emailFilter, setEmailFilter] = useState('') const [modalOpen, setModalOpen] = useState(false) - const [focussedUser, setFocussedUser] = useState(null) + const [focussedUser, setFocussedUser] = useState(null) // React-hook-form declaration const form = useForm({ @@ -67,7 +67,7 @@ const UserTable = (): JSX.Element => { const { handleSubmit } = form const submitHandler = ({ email }: HtmlFormProps): void => { setEmailFilter(email) } - const { isLoading, data: userPage, error, mutate } = useSWR(`/api/basecamp/users?page=${currentPage}&email=${emailFilter}&type=auth0`, fetcher) + const { isLoading, data: userPage, error, mutate } = useSWR(`/api/basecamp/users?page=${currentPage}&email=${emailFilter}&type=auth0`, fetcher) if (isLoading) return
Loading...
const totalPages = Math.ceil((userPage?.total ?? 0) / (userPage?.limit ?? 0)) @@ -144,7 +144,7 @@ const UserTable = (): JSX.Element => { interface UserRowProps { index: number user: any - setFocussedUser: (arg0: User) => void + setFocussedUser: (arg0: GetUsers200ResponseOneOfInner) => void setModalOpen: (arg0: boolean) => void } @@ -182,7 +182,7 @@ const UserRow = ({ index, user, setFocussedUser, setModalOpen }: UserRowProps): const PasswordlessUsers = (): JSX.Element => { const [currentPage, setPage] = useState(0) - const { data: userPage } = useSWR(`/api/basecamp/users?page=${currentPage}&type=email`, fetcher) + const { data: userPage } = useSWR(`/api/basecamp/users?page=${currentPage}&type=email`, fetcher) const onClickHandler = (userId: string): void => { void axios.get(`/api/basecamp/migrate?id=${userId}`) diff --git a/src/components/users/FavouriteButton.tsx b/src/components/users/FavouriteButton.tsx deleted file mode 100644 index d6e4dd455..000000000 --- a/src/components/users/FavouriteButton.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { signIn, useSession } from 'next-auth/react' -import React, { useEffect, useState } from 'react' -import { APIFavouriteCollections } from '../../pages/api/user/fav' - -interface Props { - climbId?: string - areaId?: string -} - -function NoLogin (): JSX.Element { - return ( - - ) -} - -/** Assuming that there is a generic 'favourites' collection for the time being. */ -const favCollection = 'favourites' - -/** Built for rapid single-instancing. This component payloads its own network requests - * so don't go making many instances without hoisting the requests into a parent. - */ -export default function FavouriteButton ({ climbId, areaId }: Props): JSX.Element | null { - const [loading, setLoading] = useState(false) - const [isFav, setIsFav] = useState(false) - const session = useSession() - - useEffect(() => { - setLoading(true) - fetch('/api/user/fav') - .then(async res => await res.json()) - .then((collections: APIFavouriteCollections) => { - if (climbId !== undefined) { - const f = collections.climbCollections[favCollection] - if (f === undefined) { - setIsFav(false) - return - } - - setIsFav(f.includes(climbId)) - return // guard block - } - - if (areaId !== undefined) { - const f = collections.areaCollections[favCollection] - if (f === undefined) { - setIsFav(false) - return - } - - setIsFav(f.includes(areaId)) - // guard block - } - }) - .catch(console.error) - .finally(() => { - setLoading(false) - }) - }, [climbId, areaId]) - - const toggle = (): void => { - // Choose operation purely on what the current visual - // state of the button is. - setLoading(true) - let method = 'POST' - if (isFav) { - method = 'DELETE' - } - - fetch('/api/user/fav', { - method, - body: JSON.stringify({ climbId, areaId, collection: favCollection }) - }) - .then(() => setIsFav(!isFav)) - .catch(err => console.error({ err })) - .finally(() => { - setLoading(false) - }) - } - - // If there is some kind of programming error / user is un-authenticated we render the default - // interaction-devoid button - if ((climbId === undefined && areaId === null) || session.status === 'unauthenticated') { - return - } - - return ( - - ) -} diff --git a/src/js/auth/CurrentUserClient.ts b/src/js/auth/CurrentUserClient.ts deleted file mode 100644 index a5132316d..000000000 --- a/src/js/auth/CurrentUserClient.ts +++ /dev/null @@ -1,26 +0,0 @@ -import axios from 'axios' -import { IUserProfile, IWritableUserMetadata } from '../types/User' - -const client = axios.create({ - headers: { - 'content-type': 'application/json' - } -}) - -export const getUserProfile = async (): Promise => { - const res = await client.get('/api/user/profile') - if (res.status === 200) { - return res.data - } - return null -} - -export const updateUserProfile = async (profile: IWritableUserMetadata): Promise => { - const res = await client.patch( - '/api/user/profile', - profile) - if (res.status === 200) { - return res.data - } - return null -} diff --git a/src/js/auth/ManagementClient.ts b/src/js/auth/ManagementClient.ts index fb661aad2..5ef75a4a3 100644 --- a/src/js/auth/ManagementClient.ts +++ b/src/js/auth/ManagementClient.ts @@ -1,17 +1,17 @@ -import { ManagementClient as Auth0MgmtClient } from 'auth0' -import type { User, UserPage, Role } from 'auth0' +import { ManagementClient as Auth0MgmtClient, GetUsers200ResponseOneOfInner, GetOrganizationMemberRoles200ResponseOneOfInner, ApiResponse } from 'auth0' +import type { GetUsers200ResponseOneOf } from 'auth0' import { AUTH_CONFIG_SERVER } from '../../Config' -import { IWritableUserMetadata, IUserProfile, IUserMetadataOriginal } from '../types/User' +import { IUserMetadataOriginal } from '../types/User' if (AUTH_CONFIG_SERVER == null) throw new Error('AUTH_CONFIG_SERVER not defined') -const { mgmtClientId, mgmtClientSecret, issuer, clientId } = AUTH_CONFIG_SERVER +const { mgmtClientId, mgmtClientSecret, mgmtClientAudience, issuer, clientId } = AUTH_CONFIG_SERVER export const auth0ManagementClient = new Auth0MgmtClient({ domain: issuer.replace('https://', ''), clientId: mgmtClientId, clientSecret: mgmtClientSecret, - scope: 'read:users update:users create:users read:roles' + audience: mgmtClientAudience }) interface GetAllUserParams { @@ -26,7 +26,7 @@ export const getAllUsersMetadata = async ({ connectionType = 'auth0', email = '', legacy = false -}: GetAllUserParams): Promise => { +}: GetAllUserParams): Promise => { let q = 'user_metadata.uuid=*' if (legacy) { q = '' @@ -42,93 +42,31 @@ export const getAllUsersMetadata = async ({ throw new Error('Invalid type. Expect auth0 or email.') } - const userPage = await auth0ManagementClient.getUsers({ + const userPage = await auth0ManagementClient.users.getAll({ q, page, per_page: 50, search_engine: 'v3', include_totals: true }) - return userPage -} - -export const getUserProfileByNick = async (nick: string): Promise => { - const users = await auth0ManagementClient.getUsers({ q: `user_metadata.nick="${nick}"` }) - - if (users == null || (users != null && users.length === 0)) throw new Error('User not found') - - // previous passwordless account - const newEmailPasswordUsers = users.filter(u => u.user_id?.startsWith('auth0')) - if (newEmailPasswordUsers.length > 1) throw new Error('Found multiple users for ' + nick) - - return reshapeAuth0UserToProfile(newEmailPasswordUsers[0]) -} - -export const reshapeAuth0UserToProfile = (user: User): IUserProfile => { - const { user_metadata: umeta } = user - if (umeta == null || user?.user_id == null || umeta?.uuid == null) throw new Error(`Missing auth provider ID and metadata for ${user?.name ?? ''}`) - return { - authProviderId: user.user_id, - name: umeta?.name ?? '', - nick: umeta.nick, - uuid: umeta.uuid, - email: user.email ?? '', - avatar: user?.picture ?? '', - bio: umeta?.bio ?? '', - roles: umeta?.roles ?? [], - loginsCount: umeta?.loginsCount ?? 0, - website: umeta?.website ?? '', - ticksImported: umeta?.ticksImported ?? false, - collections: umeta.collections ?? {} - } -} -/** - * Given a nick name, return true if it's already in use by another other user. - * @param nick user name - * @returns true - */ -export const doesUserNameExist = async (nick: string): Promise => { - const users = await auth0ManagementClient - .getUsers({ - q: `user_metadata.nick="${nick}"`, - include_fields: true, - fields: 'email' - }) - if (users != null) { - switch (users.length) { - case 0: return false - case 1: return true - default: throw new Error('#### Oops, multiple nick names found. This should not happend! ####') - } - } - // API error - throw new Error('Unable to search the user database') + return userPage.data } -export const extractUpdatableMetadataFromProfile = ({ name, nick, bio, website, ticksImported, collections }: IWritableUserMetadata): IWritableUserMetadata => ({ - name, - nick, - bio, - website, - ticksImported, - collections -}) - /** * See https://auth0.com/docs/api/management/v2#!/Jobs/post_verification_email * @param userId Auth0 internal user id. Ex: auth0|234879238023482995 */ export const sendEmailVerification = async (userId: string): Promise => { - await auth0ManagementClient.sendEmailVerification({ user_id: userId, client_id: clientId }) + await auth0ManagementClient.jobs.verifyEmail({ user_id: userId, client_id: clientId }) } /** * Retrieves roles the user is assigned. Returns at most 50 roles. * @param userId Auth0 internal user id. Ex: auth0|234879238023482995 */ -export const getUserRoles = async (userId: string): Promise => { - return await auth0ManagementClient.getUserRoles({ id: userId, page: 0, per_page: 50 }) +export const getUserRoles = async (userId: string): Promise> => { + return await auth0ManagementClient.users.getRoles({ id: userId, page: 0, per_page: 50 }) } /** @@ -137,34 +75,32 @@ export const getUserRoles = async (userId: string): Promise => { * @param roles Array of role names (Ex: 'editor'), not Auth0 role IDs (Ex: 'rol_ds239fjdsfsd') */ export const setUserRoles = async (userId: string, roles: string[]): Promise => { - const allRoles = await auth0ManagementClient.getRoles() - - const roleIdsToRemove = allRoles.reduce((res, roleObj) => { + const allRoles = await auth0ManagementClient.users.getRoles() + const roleIdsToRemove = allRoles.data.reduce((res, roleObj) => { if (roleObj.name != null && roleObj.id != null && !roles.includes(roleObj.name)) { res.push(roleObj.id) } return res }, []) // Removes roles that the user doesn't even have, but that's ok. - if (roleIdsToRemove.length > 0) await auth0ManagementClient.removeRolesFromUser({ id: userId }, { roles: roleIdsToRemove }) + if (roleIdsToRemove.length > 0) await auth0ManagementClient.users.deleteRoles({ id: userId }, { roles: roleIdsToRemove }) - const roleIdsToAssign = allRoles.reduce((res, roleObj) => { + const roleIdsToAssign = allRoles.data.reduce((res, roleObj) => { if (roleObj.name != null && roleObj.id != null && roles.includes(roleObj.name)) { res.push(roleObj.id) } return res }, []) - if (roleIdsToAssign.length > 0) await auth0ManagementClient.assignRolestoUser({ id: userId }, { roles: roleIdsToAssign }) + if (roleIdsToAssign.length > 0) await auth0ManagementClient.users.assignRoles({ id: userId }, { roles: roleIdsToAssign }) } /** * For admins to update user metadata, including read-only portions. - * Different from updateUserProfile in CurrentUserClientEasily - * which is for users to update their own data. * Extendable to update other fields in future. * @param userId Auth0 internal user id. Ex: auth0|234879238023482995 * @param userMetadata Fields to be updated */ -export const updateUser = async (userId: string, userMetadata: Partial): Promise => { - return await auth0ManagementClient.updateUser({ id: userId }, { user_metadata: userMetadata }) +export const updateUser = async (userId: string, userMetadata: Partial): Promise => { + const res = await auth0ManagementClient.users.update({ id: userId }, { user_metadata: userMetadata }) + return res.data } diff --git a/src/js/auth/mobile.ts b/src/js/auth/mobile.ts new file mode 100644 index 000000000..5ffc749ff --- /dev/null +++ b/src/js/auth/mobile.ts @@ -0,0 +1,22 @@ +import * as Auth0 from 'auth0' + +import { AUTH_CONFIG_SERVER } from '../../Config' + +if (AUTH_CONFIG_SERVER == null) throw new Error('AUTH_CONFIG_SERVER not defined') + +if (process.env.MOBILE_AUTH_SECRET == null) { + console.warn('Mobile auth secret not found') +} + +const { clientSecret, clientId, issuer } = AUTH_CONFIG_SERVER + +// Set up Auth0 client +export const auth0Client = new Auth0.AuthenticationClient({ + domain: issuer.replace('https://', ''), + clientId, + clientSecret +}) + +export const isNullOrEmpty = (str: string | null | undefined): boolean => { + return str == null || str?.trim() === '' +} diff --git a/src/js/auth/withMobileAuth.ts b/src/js/auth/withMobileAuth.ts new file mode 100644 index 000000000..5e8f4144d --- /dev/null +++ b/src/js/auth/withMobileAuth.ts @@ -0,0 +1,22 @@ +import { NextRequest, NextResponse } from 'next/server' + +const mobileAuthSecret = process.env.MOBILE_AUTH_SECRET + +type Next13ApiHandler = (req: NextRequest) => Promise + +/** + * A high-order function to protect mobile-related auth endpoints. + * Do not use elsewhere. + */ +export const withMobileAuth = (handler: Next13ApiHandler): Next13ApiHandler => { + return async function (request: NextRequest) { + if (request.method !== 'POST') { + return NextResponse.json({ message: 'Must send POST request', status: 405 }) + } + const authHeader = request.headers.get('Secret') + if (mobileAuthSecret != null && authHeader === mobileAuthSecret) { + return await handler(request) + } + return NextResponse.json({ message: 'Unauthorized', status: 401 }) + } +} diff --git a/src/pages/api/basecamp/migrate.ts b/src/pages/api/basecamp/migrate.ts deleted file mode 100644 index 070aa0c44..000000000 --- a/src/pages/api/basecamp/migrate.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { NextApiHandler } from 'next' -import { getServerSession } from 'next-auth' - -import withAuth from '../withAuth' -import { CreateUserData } from 'auth0' -import { customAlphabet } from 'nanoid' -import { nolookalikesSafe } from 'nanoid-dictionary' -import { UserRole } from '../../../js/types' - -import { auth0ManagementClient } from '../../../js/auth/ManagementClient' -import { authOptions } from '../auth/[...nextauth]' - -/** - * @deprecated This endpoint was created to migrate Auth0 passwordless accounts to - * email/password - */ -const handler: NextApiHandler = async (req, res) => { - try { - const session = await getServerSession(req, res, authOptions) - if (session?.user.metadata?.roles?.includes(UserRole.USER_ADMIN) ?? false) { - const userId = req.query?.id as string - if (userId == null) throw new Error('Invalid user id') - - const oldUser = await auth0ManagementClient.getUser({ id: userId }) - if (oldUser?.email == null || oldUser?.user_metadata == null) { - throw new Error('Missing email/user_metadata in Auth0.') - } - - const newUserMetadata = Object.assign({}, oldUser.user_metadata) - delete newUserMetadata.migrated - delete newUserMetadata.migratedDate - - const newUserData: CreateUserData = { - connection: 'Username-Password-Authentication', - email: oldUser.email, - user_metadata: newUserMetadata, - email_verified: true, - password: safeRandomString() - } - - await auth0ManagementClient.createUser(newUserData) - oldUser.user_metadata.migratedDate = new Date(Date.now()).toISOString() - - await auth0ManagementClient.updateUserMetadata({ id: userId }, oldUser.user_metadata) - res.json({ message: 'OK' }) - } else { - res.status(401).json({ message: 'Migration failed: user not authorized', errorCode: 3 }) - } - } catch (e: any) { - res.status(503).json({ message: `Unexpected error: ${e.message as string}`, errorCode: 2 }) - } -} - -const safeRandomString = customAlphabet(nolookalikesSafe, 20) - -export default withAuth(handler) diff --git a/src/pages/api/basecamp/userRoles.ts b/src/pages/api/basecamp/userRoles.ts index c671ff9ba..806cb4bde 100644 --- a/src/pages/api/basecamp/userRoles.ts +++ b/src/pages/api/basecamp/userRoles.ts @@ -42,7 +42,7 @@ const handler: NextApiHandler = async (req, res) => { async function handleGetRequest (req: NextApiRequest, res: NextApiResponse): Promise { const rolesResp = await getUserRoles(req.query.userId as string) - res.json(rolesResp) + res.json(rolesResp.data) } async function handlePostRequest (req: NextApiRequest, res: NextApiResponse): Promise { diff --git a/src/pages/api/user/exists.ts b/src/pages/api/user/exists.ts deleted file mode 100644 index 39fa06d28..000000000 --- a/src/pages/api/user/exists.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NextApiHandler } from 'next' - -import withAuth from '../withAuth' -import { doesUserNameExist } from '../../../js/auth/ManagementClient' - -/** - * Endpoint: /api/user/exists?nick= - * @returns data { found: boolean} - */ -const handler: NextApiHandler = async (req, res) => { - if (req.query?.nick as string == null) { - res.status(400).json({ error: 'Missing \'nick\' in query string' }) - } - - try { - res.setHeader('Cache-Control', 'no-store') - const found = await doesUserNameExist((req.query?.nick as string).toLowerCase()) - if (found) { - res.status(200).json({ found: true }) - } else { - res.status(200).json({ found: false }) - } - } catch (e) { - res.status(500).json({ error: 'Can\'t verify whether the user name exists. Don\'t allow the user to use this name' }) - } -} - -export default withAuth(handler) diff --git a/src/pages/api/user/fav.ts b/src/pages/api/user/fav.ts deleted file mode 100644 index 2866fde55..000000000 --- a/src/pages/api/user/fav.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { NextApiHandler } from 'next' -import withAuth from '../withAuth' -import createMetadataClient, { Auth0UserMetadata } from './metadataClient' - -type CollectionType = string[] -type CollectionCollectionType = Map -/** - * Collections are defined as being extremely generic. These are the - * specific entity collections that are favourites. Ticks, for example, - * may vary in the type of data that needs to be enumerated. - */ -interface ReifiedFavouriteCollections { - climbCollections: CollectionCollectionType - areaCollections: CollectionCollectionType -} - -export interface APIFavouriteCollections { - climbCollections: { [key: string]: string[] | undefined } - areaCollections: { [key: string]: string[] | undefined } -} - -/** Body params we expect to recieve */ -interface BodyType { - climbId?: string | string[] - areaId?: string | string[] - collection?: string | string[] -} - -function reifyCollections (meta: Auth0UserMetadata): ReifiedFavouriteCollections { - return { - climbCollections: ((meta?.collections?.climbCollections) != null) - ? new Map(Object.entries(meta?.collections?.climbCollections)) - : new Map(Object.entries({})) as CollectionCollectionType, - - areaCollections: ((meta?.collections?.areaCollections) != null) - ? new Map(Object.entries(meta?.collections?.areaCollections)) - : new Map(Object.entries({})) as CollectionCollectionType - } -} - -function backToJSONSafe (collections: ReifiedFavouriteCollections): APIFavouriteCollections { - return { - climbCollections: Object.fromEntries(collections.climbCollections), - areaCollections: Object.fromEntries(collections.areaCollections) - } -} - -/** This can be called repeatedly without causing problems. - * we interpret the favs as a set, so it's kinda whatever. - */ -const handler: NextApiHandler = async (req, res) => { - try { - const metadataClient = await createMetadataClient(req, res) - if (metadataClient == null) throw new Error('Can\'t create ManagementAPI client') - - /** - * within this closure, this meta object will be mutated a fair bit. - * At the very end, it will be committed (except in the case of a GET request) - */ - const meta = await metadataClient.getUserMetadata() - const collections = reifyCollections(meta) - if (req.method === 'GET') { - // This is a bit of a hack. We don't want to return the whole metadata object. - // We just want to return the favs. - res.json(backToJSONSafe(collections)) - res.end() - return - } - - const body: BodyType = JSON.parse(req.body) - - if (body.climbId === undefined && body.areaId === undefined) { - throw new Error('No climb or area id provided. At least one must be supplied') - } - - if (body.collection === undefined) { - body.collection = 'favourites' - } - - // I'm hoping this is an edge-case. Can't say for sure if devs should allow users to functionally - // access this endpoint without doing SOME checks first. Still, the guards are in place. - if (meta?.nick === null || meta === undefined) { - throw new Error('Un-authenticated users cannot have favs') - } - - /** This piece of code operated within the given request context, and does - * not know or understand which collections in the 'collections' object are - * supposed to be collections of favourites. - * - * There is therefore nothing except developer oversight to prevent collections - * of entites from being conflated. - * - * _metaKey is the key in the collections object that we want to mutate - * [collectionScope][_metaKey] is the general bucket of favourites, for example. - * ['areaCollections']['favourites'] is the general bucket of favourites, for example. - * - * reqKey can be considered the entity type. - */ - function addRemoveFavourite ( - reqKey: keyof typeof body, - collectionScope: keyof ReifiedFavouriteCollections, - metaKey: string - ): void { - // ensure that an id has been supplied for this entity type - const targetEntity = body[reqKey] - if (targetEntity === undefined) return - - // we mutate as a set, but store as an array - const favs: Set = new Set(collections[collectionScope].get(metaKey)) - const op = req.method === 'DELETE' - ? (id: string) => favs.delete(id) - : (id: string) => favs.add(id) - - // Rest params may be lists, we can handle that easily - if (typeof targetEntity === 'string') { - op(targetEntity) - } else { - targetEntity.forEach((id: string) => { - op(id) - }) - } - - collections[collectionScope].set(metaKey, Array.from(favs)) - } - - // The two entities we support right now are Climbs and Areas - // Just as an intellectual exercise, we could do the following for... media or something: - // addToFavs('mediaId', 'favMedia') - if (typeof body.collection === 'string') { - addRemoveFavourite('climbId', 'climbCollections', body.collection) - addRemoveFavourite('areaId', 'areaCollections', body.collection) - } else { - body.collection.forEach((targetCollection) => { - addRemoveFavourite('climbId', 'climbCollections', targetCollection) - addRemoveFavourite('areaId', 'areaCollections', targetCollection) - }) - } - - // make the meta collections object reflect the new one - // TS should flag if we make any massive mistake here - meta.collections = { - climbCollections: Object.fromEntries(collections.climbCollections), - areaCollections: Object.fromEntries(collections.areaCollections) - } - - // Commit the changes to the user's metadata - await metadataClient.updateUserMetadata(meta) - res.status(200).end() - } catch (e: any) { - // Could not add this climb to the user's favourites. - // Almost certainly a programmer error. - res.status(500).json({ error: e.message }) - } -} - -export default withAuth(handler) diff --git a/src/pages/api/user/metadataClient.ts b/src/pages/api/user/metadataClient.ts deleted file mode 100644 index 8c21e8f9d..000000000 --- a/src/pages/api/user/metadataClient.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { NextApiRequest, NextApiResponse } from 'next' -import { getServerSession } from 'next-auth' - -import { reshapeAuth0UserToProfile, extractUpdatableMetadataFromProfile, auth0ManagementClient } from '../../../js/auth/ManagementClient' -import { IUserProfile } from '../../../js/types/User' -import { authOptions } from '../auth/[...nextauth]' - -const allowedFields = ['name', 'nick', 'bio', 'website', 'ticksImported', 'collections'] as const -type AllowedField = typeof allowedFields[number] - -const isString = (value: any): value is string => typeof value === 'string' -const isBoolean = (value: any): value is boolean => typeof value === 'boolean' - -const dataTypeCheck: { [field in AllowedField]: (value: any) => boolean } = { - name: isString, - nick: isString, - bio: isString, - website: isString, - ticksImported: isBoolean, - collections: (value: any) => typeof value === 'object' && value != null -} -/** - * This is the interface for the tick object - * We are starting with name, uuid, notes - * - */ - -export interface Tick { - name: string - notes: string - climbId: string - userId: string | undefined - style: string - attemptType: string - dateClimbed: Date - grade: string - source: string -} - -export interface Auth0UserMetadata { - name?: string - nick?: string - uuid?: string - bio?: string - website?: string - ticksImported?: boolean - collections?: { - /** Users can organize entities into their own 'climbing playlists' - * Strictly speaking, this should always be mutated as a SET rather than an - * array. JSON does not support set serialization, but we ideally wish to minimize - * the actual space, so do whatever needs to be done to avoid ID duplicates. - * The key of each collection is its name. - */ - climbCollections?: { [key: string]: string[] } - /** - * Areas are seperated into their own collection rather than mixing them in with - * the climbs. Partially because they are different entities altogether, and partly - * because it seems sensible to me. - * The key of each collection is its name. - */ - areaCollections?: { [key: string]: string[] } - } -} - -interface MetadataClient { - getUserMetadata: () => Promise - updateUserMetadata: (metadata: Auth0UserMetadata) => Promise -} - -const createMetadataClient = async ( - req: NextApiRequest, - res: NextApiResponse -): Promise => { - const session = await getServerSession(req, res, authOptions) - if (session == null) return null - const { id, accessToken } = session as unknown as { id: string, accessToken: string } - - if (accessToken == null) { - return null - } - - const getUserMetadata = async (): Promise => { - const user = await auth0ManagementClient.getUser({ id }) - return reshapeAuth0UserToProfile(user) - } - - /** - * Push new values into the metadata store - */ - const updateUserMetadata = async ( - profile: IUserProfile - ): Promise => { - const metadata = extractUpdatableMetadataFromProfile(profile) - - // Check again that all fields are above board in terms of their types - // and names. prevents a user from injecting arbitrary data into the store - // by changing a valid field into an object. (Like submitting an object for - // their bio) - Object.keys(metadata).forEach((field) => { - // Check the field is allowed - if (!allowedFields.includes(field as AllowedField)) { - throw new Error(`Invalid field ${field}`) - } - // Check the field is above board - - // @ts-expect-error - const meta = metadata[field] - // @ts-expect-error - if (dataTypeCheck[field](meta) == null) { - throw new Error(`Invalid data type for field ${field}`) - } - }) - - // The auth0 management client only touches properties that - // are actually specified in metadata. So an undefined property - // is passed over, not nulled, by Auth0 - const currentMeta = await getUserMetadata() - if (currentMeta.nick === metadata.nick) { - delete currentMeta.nick - } - - // Actually write this new data into the users profile. - const user = await auth0ManagementClient.updateUserMetadata( - { id }, - metadata - ) - return user?.user_metadata ?? {} - } - - return { - getUserMetadata, - // @ts-expect-error issue https://github.com/OpenBeta/open-tacos/issues/936 - updateUserMetadata - } -} - -export default createMetadataClient diff --git a/src/pages/api/user/ticks.ts b/src/pages/api/user/ticks.ts index 0dbcbe770..a6d4cabcd 100644 --- a/src/pages/api/user/ticks.ts +++ b/src/pages/api/user/ticks.ts @@ -1,10 +1,25 @@ import { NextApiHandler } from 'next' +import { getServerSession } from 'next-auth' import csv from 'csvtojson' -import withAuth from '../withAuth' -import createMetadataClient, { Tick } from './metadataClient' import axios, { AxiosInstance } from 'axios' import { v5 as uuidv5, NIL } from 'uuid' +import withAuth from '../withAuth' +import { authOptions } from '../auth/[...nextauth]' +import { updateUser } from '@/js/auth/ManagementClient' + +export interface Tick { + name: string + notes: string + climbId: string + userId: string | undefined + style: string + attemptType: string + dateClimbed: Date + grade: string + source: string +} + const MP_ID_REGEX: RegExp = /route\/(?\d+)\// /** * @@ -63,47 +78,40 @@ async function getMPTicks (uid: string): Promise { } const handler: NextApiHandler = async (req, res) => { - try { - const metadataClient = await createMetadataClient(req, res) - if (metadataClient == null) throw new Error('Can\'t create ManagementAPI client') - const meta = await metadataClient.getUserMetadata() - if (req.method === 'GET') { - res.end() - } else if (req.method === 'POST') { - // fetch data from mountain project here - const uid: string = JSON.parse(req.body) - const tickCollection: Tick[] = [] - if (uid.length > 0 && meta.uuid !== undefined) { - const ret = await getMPTicks(uid) - ret.forEach((tick) => { - const newTick: Tick = { - name: tick.Route, - notes: tick.Notes, - climbId: tick.mp_id, - userId: meta.uuid, - style: tick.Style === '' ? 'N/A' : tick.Style, - attemptType: tick.Style === '' ? 'N/A' : tick.Style, - dateClimbed: new Date(Date.parse(`${tick.Date}T00:00:00`)), // Date.parse without timezone specified converts date to user's present timezone. - grade: tick.Rating, - source: 'MP' - } - tickCollection.push(newTick) - }) - // set the user flag to true, so the popup doesn't show anymore and - // update the metadata - meta.ticksImported = true - await metadataClient.updateUserMetadata(meta) - // return the new ticks object - res.json({ ticks: tickCollection }) - res.end() - } - } else if (req.method === 'PUT') { - meta.ticksImported = true - await metadataClient.updateUserMetadata(meta) - res.status(200).end() + if (req.method !== 'POST') res.end() + const session = await getServerSession(req, res, authOptions) + if (session == null) res.end() + + const uuid = session?.user.metadata?.uuid + const uid: string = JSON.parse(req.body) + if (uuid == null || uid == null) res.status(500) + + // fetch data from mountain project here + const tickCollection: Tick[] = [] + const ret = await getMPTicks(uid) + + ret.forEach((tick) => { + const newTick: Tick = { + name: tick.Route, + notes: tick.Notes, + climbId: tick.mp_id, + userId: uuid, + style: tick.Style === '' ? 'N/A' : tick.Style, + attemptType: tick.Style === '' ? 'N/A' : tick.Style, + dateClimbed: new Date(Date.parse(`${tick.Date}T00:00:00`)), // Date.parse without timezone specified converts date to user's present timezone. + grade: tick.Rating, + source: 'MP' } - } catch (e: any) { - res.status(500).json({ error: e.message }) + tickCollection.push(newTick) + }) + // set the user flag to true, so the popup doesn't show anymore and + // update the metadata + // Note: null check is to make TS happy. We wouldn't get here if session is null. + if (session != null) { + await updateUser(session.id, { ticksImported: true }) } + + res.json({ ticks: tickCollection }) + res.end() } export default withAuth(handler) diff --git a/src/pages/p/[...slug].tsx b/src/pages/p/[...slug].tsx deleted file mode 100644 index 55c83e4af..000000000 --- a/src/pages/p/[...slug].tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { NextPage, GetStaticProps } from 'next' -// import { useRouter } from 'next/router' -// import dynamic from 'next/dynamic' - -// import Layout from '../../components/layout' -// import SeoTags from '../../components/SeoTags' -// import { getUserMedia } from '../../js/graphql/api' -// import { getUserImages, getFileInfo } from '../../js/sirv/SirvClient' -import { IUserProfile, MediaWithTags } from '../../js/types' -import { getUserProfileByNick } from '../../js/auth/ManagementClient' -// import usePermissions from '../../js/hooks/auth/usePermissions' -// import { useUserProfileSeo } from '../../js/hooks/seo' -// import type { UserSingleImageViewProps } from '../../components/media/UserSingleImageView' - -interface UserSinglePostViewProps { - uid: string - postId: string | null - serverMediaList: MediaWithTags[] - userProfile: IUserProfile -} - -const UserSinglePostView: NextPage = ({ uid, postId = null, serverMediaList, userProfile }) => { - // const router = useRouter() - // const auth = usePermissions({ ownerProfileOnPage: userProfile }) - - // const { isAuthorized } = auth - - // const { author, pageTitle, pageImages } = useUserProfileSeo({ - // username: uid, - // fullName: userProfile?.name, - // imageList: serverMediaList - // }) - - // const { isFallback } = router - - return ( - <> - {/* */} - {/* - -
- - {!isAuthorized && !isFallback && ( -
- All photos are copyrighted by their respective owners. All Rights Reserved. -
- )} -
-
*/} - - - ) -} -export default UserSinglePostView - -export async function getStaticPaths (): Promise { - return { - paths: [], - fallback: true - } -} - -export const getStaticProps: GetStaticProps = async ({ params }) => { - const uid = params?.slug?.[0] ?? null - // const postId = params?.slug?.[1] ?? null - - if (uid == null) { - return { notFound: true } - } - - try { - const userProfile = await getUserProfileByNick(uid) - - if (userProfile?.uuid == null) { - throw new Error('Bad user profile data') - } - - // const { uuid } = userProfile - // const filename = postId != null ? `/u/${uuid}/${postId}` : null - - // const list = await getUserMedia(uuid, 4) - - // const data = { - // uid, - // postId, - // serverMediaList: [], - // userProfile - // } - return { - notFound: true // Just direct to a 404. I'll fix this in another PR. - // props: data, - // revalidate: 120 - } - } catch (e) { - console.log('Error in getStaticProps()', e) - return { - notFound: true, - revalidate: 120 - } - } -} - -// const DynamicUserFeatureImageview = dynamic( -// async () => -// await import('../../components/media/UserSingleImageView').then( -// module => module.default), { ssr: true } -// ) diff --git a/yarn.lock b/yarn.lock index 016716b87..ed472c0c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2417,11 +2417,6 @@ resolved "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.1.tgz" integrity sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg== -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - "@tootallnate/once@2": version "2.0.0" resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" @@ -2519,21 +2514,6 @@ dependencies: "@babel/types" "^7.20.7" -"@types/body-parser@*": - version "1.19.3" - resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz" - integrity sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.36" - resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz" - integrity sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w== - dependencies: - "@types/node" "*" - "@types/d3-array@^3.0.3": version "3.0.7" resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.7.tgz" @@ -2597,41 +2577,6 @@ dependencies: "@types/ms" "*" -"@types/express-jwt@0.0.42": - version "0.0.42" - resolved "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz" - integrity sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag== - dependencies: - "@types/express" "*" - "@types/express-unless" "*" - -"@types/express-serve-static-core@^4.17.33": - version "4.17.36" - resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz" - integrity sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express-unless@*": - version "2.0.1" - resolved "https://registry.npmjs.org/@types/express-unless/-/express-unless-2.0.1.tgz" - integrity sha512-PJLiNw03EjkWDkQbhNjIXXDLObC3eMQhFASDV+WakFbT8eL7YdjlbV6MXd3Av5Lejq499d6pFuV1jyK+EHyG3Q== - dependencies: - express-unless "*" - -"@types/express@*": - version "4.17.17" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz" - integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - "@types/file-saver@^2.0.5": version "2.0.5" resolved "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz" @@ -2668,11 +2613,6 @@ dependencies: "@types/unist" "*" -"@types/http-errors@*": - version "2.0.2" - resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz" - integrity sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg== - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" @@ -2757,16 +2697,6 @@ dependencies: "@types/unist" "*" -"@types/mime@*": - version "3.0.1" - resolved "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - -"@types/mime@^1": - version "1.3.2" - resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz" - integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== - "@types/ms@*": version "0.7.34" resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz" @@ -2802,16 +2732,6 @@ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.6.tgz" integrity sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg== -"@types/qs@*": - version "6.9.8" - resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz" - integrity sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - "@types/react-beautiful-dnd@^13.1.0": version "13.1.4" resolved "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.4.tgz" @@ -2845,23 +2765,6 @@ resolved "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz" integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw== -"@types/send@*": - version "0.17.1" - resolved "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz" - integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-static@*": - version "1.15.2" - resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz" - integrity sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw== - dependencies: - "@types/http-errors" "*" - "@types/mime" "*" - "@types/node" "*" - "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" @@ -3271,11 +3174,6 @@ arrify@^2.0.0: resolved "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -asap@^2.0.0: - version "2.0.6" - resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz" - integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz" @@ -3305,18 +3203,14 @@ attr-accept@^2.2.2: resolved "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz" integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg== -auth0@^2.42.0: - version "2.44.1" - resolved "https://registry.npmjs.org/auth0/-/auth0-2.44.1.tgz" - integrity sha512-+/lloZ2YGa8Epf2e1TRhyeXNEN1xHd4mw7arhYvD0ZY83ZOZxsHtaVyaOXuSL+rp0uqXHjyNjAbkGeyYxTUe7Q== +auth0@^4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/auth0/-/auth0-4.12.0.tgz#8fdce638ab837e9a740adb9068139a90058def07" + integrity sha512-5WDAHb8EvWSmRyA9D+FTBrHdEL1RM48PTPHVPxSmzbiAXrhR4pSgwSJyoGjia2+rvMR2NMXhtMfuRRqosEp7PA== dependencies: - axios "^0.27.2" - form-data "^3.0.1" - jsonwebtoken "^8.5.1" - jwks-rsa "^1.12.1" - lru-memoizer "^2.1.4" - rest-facade "^1.16.3" - retry "^0.13.1" + jose "^4.13.2" + undici-types "^6.15.0" + uuid "^9.0.0" autoprefixer@^10.4.1: version "10.4.16" @@ -3357,13 +3251,6 @@ awesome-only-resolves-last-promise@^1.0.3: dependencies: awesome-imperative-promise "^1.0.1" -axios@^0.21.1: - version "0.21.4" - resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz" - integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== - dependencies: - follow-redirects "^1.14.0" - axios@^0.24.0: version "0.24.0" resolved "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz" @@ -3378,14 +3265,6 @@ axios@^0.26.0: dependencies: follow-redirects "^1.14.8" -axios@^0.27.2: - version "0.27.2" - resolved "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== - dependencies: - follow-redirects "^1.14.9" - form-data "^4.0.0" - babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz" @@ -3589,14 +3468,6 @@ callsites@^3.0.0: resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camel-case@^1.1.1: - version "1.2.2" - resolved "https://registry.npmjs.org/camel-case/-/camel-case-1.2.2.tgz" - integrity sha512-rUug78lL8mqStaLehmH2F0LxMJ2TM9fnPFxb+gFkgyUjUM/1o2wKTQtalypHnkb2cFwH/DENBw7YEAOYLgSMxQ== - dependencies: - sentence-case "^1.1.1" - upper-case "^1.1.1" - camelcase-css@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz" @@ -3642,28 +3513,6 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -change-case@^2.3.0: - version "2.3.1" - resolved "https://registry.npmjs.org/change-case/-/change-case-2.3.1.tgz" - integrity sha512-3HE5jrTqqn9jeKzD0+yWi7FU4OMicLbwB57ph4bpwEn5jGi3hZug5WjZjnBD2RY7YyTKAAck86ACfShXUWJKLg== - dependencies: - camel-case "^1.1.1" - constant-case "^1.1.0" - dot-case "^1.1.0" - is-lower-case "^1.1.0" - is-upper-case "^1.1.0" - lower-case "^1.1.1" - lower-case-first "^1.0.0" - param-case "^1.1.0" - pascal-case "^1.1.0" - path-case "^1.1.0" - sentence-case "^1.1.1" - snake-case "^1.1.0" - swap-case "^1.1.0" - title-case "^1.1.0" - upper-case "^1.1.1" - upper-case-first "^1.1.0" - char-regex@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" @@ -3784,11 +3633,6 @@ commander@^7.2.0: resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -component-emitter@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - compressible@^2.0.12: version "2.0.18" resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz" @@ -3801,14 +3645,6 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -constant-case@^1.1.0: - version "1.1.2" - resolved "https://registry.npmjs.org/constant-case/-/constant-case-1.1.2.tgz" - integrity sha512-FQ/HuOuSnX6nIF8OnofRWj+KnOpGAHXQpOKHmsL1sAnuLwu6r5mHGK+mJc0SkHkbmNfcU/SauqXLTEOL1JQfJA== - dependencies: - snake-case "^1.1.0" - upper-case "^1.1.1" - convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.9.0" resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz" @@ -3824,11 +3660,6 @@ cookie@^0.5.0: resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== -cookiejar@^2.1.3: - version "2.1.4" - resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz" - integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== - copy-anything@^3.0.2: version "3.0.5" resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-3.0.5.tgz#2d92dce8c498f790fa7ad16b01a1ae5a45b020a0" @@ -4153,11 +3984,6 @@ deep-is@^0.1.3: resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^3.2.0: - version "3.3.0" - resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz" - integrity sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA== - deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" @@ -4208,14 +4034,6 @@ devlop@^1.0.0: dependencies: dequal "^2.0.0" -dezalgo@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz" - integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== - dependencies: - asap "^2.0.0" - wrappy "1" - diacritics@1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz" @@ -4306,13 +4124,6 @@ domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" -dot-case@^1.1.0: - version "1.1.2" - resolved "https://registry.npmjs.org/dot-case/-/dot-case-1.1.2.tgz" - integrity sha512-NzEIt12UjECXi6JZ/R/nBey6EE1qCN0yUTEFaPIaKW0AcOEwlKqujtcJVbtSfLNnj3CDoXLQyli79vAaqohyvw== - dependencies: - sentence-case "^1.1.2" - duplexify@^4.0.0: version "4.1.2" resolved "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz" @@ -4825,11 +4636,6 @@ expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -express-unless@*: - version "2.1.3" - resolved "https://registry.npmjs.org/express-unless/-/express-unless-2.1.3.tgz" - integrity sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ== - extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz" @@ -4886,11 +4692,6 @@ fast-loops@^1.1.3: resolved "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.3.tgz" integrity sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g== -fast-safe-stringify@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz" - integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== - fast-shallow-equal@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz" @@ -5003,7 +4804,7 @@ flatted@^3.2.7: resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== -follow-redirects@^1.14.0, follow-redirects@^1.14.4, follow-redirects@^1.14.8, follow-redirects@^1.14.9: +follow-redirects@^1.14.4, follow-redirects@^1.14.8: version "1.15.3" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz" integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== @@ -5015,15 +4816,6 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -form-data@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - form-data@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" @@ -5033,16 +4825,6 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -formidable@^2.0.1: - version "2.1.2" - resolved "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz" - integrity sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g== - dependencies: - dezalgo "^1.0.4" - hexoid "^1.0.0" - once "^1.4.0" - qs "^6.11.0" - fraction.js@^4.3.6: version "4.3.6" resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz" @@ -5380,11 +5162,6 @@ hast-util-whitespace@^3.0.0: dependencies: "@types/hast" "^3.0.0" -hexoid@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz" - integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== - hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" @@ -5419,15 +5196,6 @@ html-url-attributes@^3.0.0: resolved "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.0.tgz" integrity sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow== -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz" @@ -5690,13 +5458,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-lower-case@^1.1.0: - version "1.1.3" - resolved "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz" - integrity sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA== - dependencies: - lower-case "^1.1.0" - is-map@^2.0.1, is-map@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz" @@ -5787,13 +5548,6 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: dependencies: which-typed-array "^1.1.11" -is-upper-case@^1.1.0: - version "1.1.2" - resolved "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz" - integrity sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw== - dependencies: - upper-case "^1.1.0" - is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz" @@ -6290,6 +6044,11 @@ jose@^4.11.4, jose@^4.14.4: resolved "https://registry.npmjs.org/jose/-/jose-4.14.6.tgz" integrity sha512-EqJPEUlZD0/CSUMubKtMaYUOtWe91tZXTWMJZoKSbLk+KtdhNdcvppH8lA9XwVu2V4Ailvsj0GBZJ2ZwDjfesQ== +jose@^4.13.2: + version "4.15.9" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.9.tgz#9b68eda29e9a0614c042fa29387196c7dd800100" + integrity sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA== + js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz" @@ -6411,22 +6170,6 @@ json5@^2.2.3: resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonwebtoken@^8.5.1: - version "8.5.1" - resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz" - integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^5.6.0" - "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.3.5" resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz" @@ -6437,15 +6180,6 @@ jsonwebtoken@^8.5.1: object.assign "^4.1.4" object.values "^1.1.6" -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - jwa@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz" @@ -6455,30 +6189,6 @@ jwa@^2.0.0: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" -jwks-rsa@^1.12.1: - version "1.12.3" - resolved "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.12.3.tgz" - integrity sha512-cFipFDeYYaO9FhhYJcZWX/IyZgc0+g316rcHnDpT2dNRNIE/lMOmWKKqp09TkJoYlNFzrEVODsR4GgXJMgWhnA== - dependencies: - "@types/express-jwt" "0.0.42" - axios "^0.21.1" - debug "^4.1.0" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - jsonwebtoken "^8.5.1" - limiter "^1.1.5" - lru-memoizer "^2.1.2" - ms "^2.1.2" - proxy-from-env "^1.1.0" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - jws@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz" @@ -6532,11 +6242,6 @@ lilconfig@^2.0.5, lilconfig@^2.1.0: resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz" integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== -limiter@^1.1.5: - version "1.1.5" - resolved "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz" - integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== - lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" @@ -6592,61 +6297,21 @@ lodash.castarray@^4.4.0: resolved "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz" integrity sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q== -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz" - integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== - lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz" - integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== - -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz" - integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== - -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz" - integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== - -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz" - integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz" - integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== - lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz" integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz" - integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz" - integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== - lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.3: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" @@ -6664,18 +6329,6 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lower-case-first@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz" - integrity sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA== - dependencies: - lower-case "^1.1.2" - -lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2: - version "1.1.4" - resolved "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz" - integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA== - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" @@ -6690,22 +6343,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-cache@~4.0.0: - version "4.0.2" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz" - integrity sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw== - dependencies: - pseudomap "^1.0.1" - yallist "^2.0.0" - -lru-memoizer@^2.1.2, lru-memoizer@^2.1.4: - version "2.2.0" - resolved "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz" - integrity sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw== - dependencies: - lodash.clonedeep "^4.5.0" - lru-cache "~4.0.0" - lz-string@^1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz" @@ -6847,11 +6484,6 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -methods@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - micromark-core-commonmark@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz" @@ -7066,11 +6698,6 @@ mime-types@^2.0.8, mime-types@^2.1.12: dependencies: mime-db "1.52.0" -mime@2.6.0: - version "2.6.0" - resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - mime@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz" @@ -7103,7 +6730,7 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.1, ms@^2.1.2: +ms@^2.1.1: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -7465,13 +7092,6 @@ paperjs-offset@^1.0.8: dependencies: paper "^0.12.4" -param-case@^1.1.0: - version "1.1.2" - resolved "https://registry.npmjs.org/param-case/-/param-case-1.1.2.tgz" - integrity sha512-gksk6zeZQxwBm1AHsKh+XDFsTGf1LvdZSkkpSIkfDtzW+EQj/P2PBgNb3Cs0Y9Xxqmbciv2JZe3fWU6Xbher+Q== - dependencies: - sentence-case "^1.1.2" - parent-module@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" @@ -7504,21 +7124,6 @@ parse5@^7.0.0, parse5@^7.1.1: dependencies: entities "^4.4.0" -pascal-case@^1.1.0: - version "1.1.2" - resolved "https://registry.npmjs.org/pascal-case/-/pascal-case-1.1.2.tgz" - integrity sha512-QWlbdQHdKWlcyTEuv/M0noJtlCa7qTmg5QFAqhx5X9xjAfCU1kXucL+rcOmd2HliESuRLIOz8521RAW/yhuQog== - dependencies: - camel-case "^1.1.1" - upper-case-first "^1.1.0" - -path-case@^1.1.0: - version "1.1.2" - resolved "https://registry.npmjs.org/path-case/-/path-case-1.1.2.tgz" - integrity sha512-2snAGA6xVRqTuTPa40bn0iEpYtVK6gEqeyS/63dqpm5pGlesOv6EmRcnB9Rr6eAnAC2Wqlbz0tqgJZryttxhxg== - dependencies: - sentence-case "^1.1.2" - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" @@ -7781,16 +7386,6 @@ proxy-compare@2.4.0: resolved "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.4.0.tgz" integrity sha512-FD8KmQUQD6Mfpd0hywCOzcon/dbkFP8XBd9F1ycbKtvVsfv6TsFUKJ2eC0Iz2y+KzlkdT1Z8SY6ZSgm07zOyqg== -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -pseudomap@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz" - integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== - psl@^1.1.33: version "1.9.0" resolved "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz" @@ -7806,13 +7401,6 @@ pure-rand@^6.0.0: resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.3.tgz" integrity sha512-KddyFewCsO0j3+np81IQ+SweXLDnDQTs5s67BOnrYmYe/yNmUhttQyGsYzy8yUnoljGAQ9sl38YB4vH8ur7Y+w== -qs@^6.10.3, qs@^6.11.0: - version "6.11.2" - resolved "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz" - integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== - dependencies: - side-channel "^1.0.4" - querystringify@^2.1.1: version "2.2.0" resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz" @@ -8071,7 +7659,7 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" -readable-stream@^3.1.1, readable-stream@^3.6.0: +readable-stream@^3.1.1: version "3.6.2" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -8287,16 +7875,6 @@ response-iterator@^0.2.6: resolved "https://registry.npmjs.org/response-iterator/-/response-iterator-0.2.6.tgz" integrity sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw== -rest-facade@^1.16.3: - version "1.16.4" - resolved "https://registry.npmjs.org/rest-facade/-/rest-facade-1.16.4.tgz" - integrity sha512-EeQm4TMYFAvEw/6wV0OyjerdR8V2cThnmXuPCmRWSrwG6p2fZw9ZkzMIYy33OpdnvHCoGHggKOly7J6Nu3nsAQ== - dependencies: - change-case "^2.3.0" - deepmerge "^3.2.0" - lodash.get "^4.4.2" - superagent "^7.1.3" - retry-request@^5.0.0: version "5.0.2" resolved "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz" @@ -8305,7 +7883,7 @@ retry-request@^5.0.0: debug "^4.1.1" extend "^3.0.2" -retry@0.13.1, retry@^0.13.1: +retry@0.13.1: version "0.13.1" resolved "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz" integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== @@ -8389,11 +7967,6 @@ screenfull@^5.1.0: resolved "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz" integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA== -semver@^5.6.0: - version "5.7.2" - resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" @@ -8406,13 +7979,6 @@ semver@^7.0.0, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: dependencies: lru-cache "^6.0.0" -sentence-case@^1.1.1, sentence-case@^1.1.2: - version "1.1.3" - resolved "https://registry.npmjs.org/sentence-case/-/sentence-case-1.1.3.tgz" - integrity sha512-laa/UDTPXsrQnoN/Kc8ZO7gTeEjMsuPiDgUCk9N0iINRZvqAMCTXjGl8+tD27op1eF/JHbdUlEUmovDh6AX7sA== - dependencies: - lower-case "^1.1.1" - set-function-name@^2.0.0, set-function-name@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz" @@ -8488,13 +8054,6 @@ slugify@^1.6.6: resolved "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz" integrity sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw== -snake-case@^1.1.0: - version "1.1.2" - resolved "https://registry.npmjs.org/snake-case/-/snake-case-1.1.2.tgz" - integrity sha512-oapUKC+qulnUIN+/O7Tbl2msi9PQvJeivGN9RNbygxzI2EOY0gA96i8BJLYnGUWSLGcYtyW4YYqnGTZEySU/gg== - dependencies: - sentence-case "^1.1.2" - sort-asc@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz" @@ -8786,23 +8345,6 @@ sucrase@^3.32.0: pirates "^4.0.1" ts-interface-checker "^0.1.9" -superagent@^7.1.3: - version "7.1.6" - resolved "https://registry.npmjs.org/superagent/-/superagent-7.1.6.tgz" - integrity sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g== - dependencies: - component-emitter "^1.3.0" - cookiejar "^2.1.3" - debug "^4.3.4" - fast-safe-stringify "^2.1.1" - form-data "^4.0.0" - formidable "^2.0.1" - methods "^1.1.2" - mime "2.6.0" - qs "^6.10.3" - readable-stream "^3.6.0" - semver "^7.3.7" - supercluster@^8.0.0, supercluster@^8.0.1: version "8.0.1" resolved "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz" @@ -8861,14 +8403,6 @@ svgo@^2.8.0: picocolors "^1.0.0" stable "^0.1.8" -swap-case@^1.1.0: - version "1.1.2" - resolved "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz" - integrity sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ== - dependencies: - lower-case "^1.1.1" - upper-case "^1.1.1" - swr@^2.1.5: version "2.2.2" resolved "https://registry.npmjs.org/swr/-/swr-2.2.2.tgz" @@ -9007,14 +8541,6 @@ tinyqueue@^2.0.3: resolved "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz" integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA== -title-case@^1.1.0: - version "1.1.2" - resolved "https://registry.npmjs.org/title-case/-/title-case-1.1.2.tgz" - integrity sha512-xYbo5Um5MBgn24xJSK+x5hZ8ehuGXTVhgx32KJCThHRHwpyIb1lmABi1DH5VvN9E7rNEquPjz//rF/tZQd7mjQ== - dependencies: - sentence-case "^1.1.1" - upper-case "^1.0.3" - tmpl@1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" @@ -9247,6 +8773,11 @@ underscore@^1.13.3: resolved "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz" integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== +undici-types@^6.15.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" @@ -9344,18 +8875,6 @@ update-browserslist-db@^1.0.13: escalade "^3.1.1" picocolors "^1.0.0" -upper-case-first@^1.1.0: - version "1.1.2" - resolved "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz" - integrity sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ== - dependencies: - upper-case "^1.1.1" - -upper-case@^1.0.3, upper-case@^1.1.0, upper-case@^1.1.1: - version "1.1.3" - resolved "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz" - integrity sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA== - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" @@ -9647,11 +9166,6 @@ y18n@^5.0.5: resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^2.0.0: - version "2.1.2" - resolved "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz" - integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== - yallist@^3.0.2: version "3.1.1" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" From c7d71815c6152bc95e4f5bff6d7e59c8634648dc Mon Sep 17 00:00:00 2001 From: Viet Nguyen <3805254+vnugent@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:13:14 -0800 Subject: [PATCH 2/2] fix: prod tenant requires explicit realm (#1211) * fix: prod tenant requires explicit realm * fix[bb]: missing env var --- .env | 1 + src/app/api/mobile/login/route.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.env b/.env index bf901343a..aaa47e8e2 100644 --- a/.env +++ b/.env @@ -11,6 +11,7 @@ NEXTAUTH_SECRET=vQyFR7gskaqxehN0cI/53r+duWc5Et0ktdoz6KozTCo= # Auth0 Management API AUTH0_MGMT_CLIENT_ID=Ecyj4oke3Cpk1khRsdKr8njen6ZZKePF AUTH0_MGMT_CLIENT_SECRET=send request to hello at openbeta.io +AUTH0_MGMT_CLIENT_AUDIENCE=https://dev-fmjy7n5n.us.auth0.com/api/v2/ ######### Client-side vars ############ # Must prefix with NEXT_PUBLIC_in order to expose vars to the browser diff --git a/src/app/api/mobile/login/route.ts b/src/app/api/mobile/login/route.ts index 14b408ed6..a161187d1 100644 --- a/src/app/api/mobile/login/route.ts +++ b/src/app/api/mobile/login/route.ts @@ -27,7 +27,8 @@ async function postHandler (request: NextRequest): Promise { username, password, scope: 'openid profile email offline_access', - audience: 'https://api.openbeta.io' + audience: 'https://api.openbeta.io', + realm: 'Username-Password-Authentication' }) return NextResponse.json({ data: response.data })