mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
Add support for scopes oAuth apps (#17976)
* Bump @supabase/shared-types. * Add scopes parameter to all queries and mutations. * Refactor the publish app side panel to include scopes. * Refactor the authorize page to show the app scopes. * Remove a console.log. * Address all feedback. * Simplify the scope rendering. * Simplify the scopes wording when creating an app. * Make the modal a bit wider so that some of the text doesn't overflow. * Add docs url to oauth scopes --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
1 parent
c4320bd0ae
commit
dbccdf8beb
11 files changed
+356
-28
No files matched your search
Generated
+6
-6
@@ -15279,11 +15279,6 @@
|
||||
"websocket": "^1.0.34"
|
||||
}
|
||||
},
|
||||
"node_modules/@supabase/shared-types": {
|
||||
"version": "0.1.47",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/shared-types/-/shared-types-0.1.47.tgz",
|
||||
"integrity": "sha512-OsXFgKd7yID/5mHuQ8k7I+dCT++ctEEj4YqV8soAik2EP0oiqqRzPwJ8kdmpwK+YyB0Z+AfOxex4dVa7J9J6eA=="
|
||||
},
|
||||
"node_modules/@supabase/storage-js": {
|
||||
"version": "2.5.1",
|
||||
"license": "MIT",
|
||||
@@ -42949,7 +42944,7 @@
|
||||
"@supabase/auth-helpers-nextjs": "^0.5.6",
|
||||
"@supabase/auth-helpers-react": "^0.3.1",
|
||||
"@supabase/react-data-grid": "^7.1.0-beta.7",
|
||||
"@supabase/shared-types": "0.1.47",
|
||||
"@supabase/shared-types": "0.1.51",
|
||||
"@supabase/supabase-js": "^2.23.0",
|
||||
"@tanstack/react-query": "^4.22.0",
|
||||
"@tanstack/react-query-devtools": "^4.22.0",
|
||||
@@ -43274,6 +43269,11 @@
|
||||
"url": "https://opencollective.com/storybook"
|
||||
}
|
||||
},
|
||||
"studio/node_modules/@supabase/shared-types": {
|
||||
"version": "0.1.51",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/shared-types/-/shared-types-0.1.51.tgz",
|
||||
"integrity": "sha512-ZCm3sdxFwFDIOakciAH+or1gWQ6SKqiSWmmejsUCyH5QfMl4ngFc6hZcNEky5+NJ5wI7YtukUpvKihNVR0iNSg=="
|
||||
},
|
||||
"studio/node_modules/@tanstack/match-sorter-utils": {
|
||||
"version": "8.7.6",
|
||||
"license": "MIT",
|
||||
|
||||
+109
-10
@@ -1,13 +1,62 @@
|
||||
import { OAuthScope } from '@supabase/shared-types/out/constants'
|
||||
import { IconCheck } from 'ui'
|
||||
|
||||
export interface AuthorizeRequesterDetailsProps {
|
||||
icon: string | null
|
||||
name: string
|
||||
domain: string
|
||||
scopes: OAuthScope[]
|
||||
}
|
||||
|
||||
const AuthorizeRequesterDetails = ({ icon, name, domain }: AuthorizeRequesterDetailsProps) => {
|
||||
const ScopeSection = ({
|
||||
description,
|
||||
hasReadScope,
|
||||
hasWriteScope,
|
||||
}: {
|
||||
description: string
|
||||
hasReadScope: boolean
|
||||
hasWriteScope: boolean
|
||||
}) => {
|
||||
if (hasReadScope || hasWriteScope) {
|
||||
const perms = [hasReadScope ? 'Read' : null, hasWriteScope ? 'Write' : null]
|
||||
.filter(Boolean)
|
||||
|
||||
.map((str) => (
|
||||
<span key={str} className="font-semibold text-foreground">
|
||||
{str}
|
||||
</span>
|
||||
))
|
||||
.reduce((acc, v) => (
|
||||
<>
|
||||
{acc}
|
||||
<span> and </span>
|
||||
{v}
|
||||
</>
|
||||
))
|
||||
|
||||
return (
|
||||
<div className="first:border-t border-b flex flex-row space-x-1 text-sm text-foreground-light py-2 px-1">
|
||||
<div className="pt-0.5">
|
||||
<IconCheck stroke="green" height={18} width={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
{perms} {description}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const AuthorizeRequesterDetails = ({
|
||||
icon,
|
||||
name,
|
||||
domain,
|
||||
scopes,
|
||||
}: AuthorizeRequesterDetailsProps) => {
|
||||
return (
|
||||
<div className="flex space-x-4">
|
||||
<div>
|
||||
<div className="flex space-y-4 flex-col">
|
||||
<div className="flex flex-row space-x-4">
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className="w-14 h-14 md:w-16 md:h-16 bg-center bg-no-repeat bg-cover flex items-center justify-center rounded-md border border-scale-600"
|
||||
@@ -18,14 +67,64 @@ const AuthorizeRequesterDetails = ({ icon, name, domain }: AuthorizeRequesterDet
|
||||
{icon === null && <p className="text-foreground-light text-lg">{name[0]}</p>}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-foreground-light">
|
||||
{name} ({domain}) is requesting API access to an organization.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Permissions</h2>
|
||||
<p className="text-sm text-foreground-light">
|
||||
The following scopes will apply for the{' '}
|
||||
<span className="text-amber-900">selected organization and all of its projects.</span>
|
||||
</p>
|
||||
<div className="pt-2">
|
||||
<ScopeSection
|
||||
description="access to auth configurations and SSO providers."
|
||||
hasReadScope={scopes.includes(OAuthScope.AUTH_READ)}
|
||||
hasWriteScope={scopes.includes(OAuthScope.AUTH_WRITE)}
|
||||
/>
|
||||
<ScopeSection
|
||||
description="access to Postgres configurations, SQL snippets, SSL enforcement configurations and Typescript schema types."
|
||||
hasReadScope={scopes.includes(OAuthScope.DATABASE_READ)}
|
||||
hasWriteScope={scopes.includes(OAuthScope.DATABASE_WRITE)}
|
||||
/>
|
||||
<ScopeSection
|
||||
description="access to custom domains and vanity subdomains."
|
||||
hasReadScope={scopes.includes(OAuthScope.DOMAINS_READ)}
|
||||
hasWriteScope={scopes.includes(OAuthScope.DOMAINS_WRITE)}
|
||||
/>
|
||||
<ScopeSection
|
||||
description="access to edge functions."
|
||||
hasReadScope={scopes.includes(OAuthScope.EDGE_FUNCTIONS_READ)}
|
||||
hasWriteScope={scopes.includes(OAuthScope.EDGE_FUNCTIONS_WRITE)}
|
||||
/>
|
||||
<ScopeSection
|
||||
description="access to environments/branches."
|
||||
hasReadScope={scopes.includes(OAuthScope.ENVIRONMENT_READ)}
|
||||
hasWriteScope={scopes.includes(OAuthScope.ENVIRONMENT_WRITE)}
|
||||
/>
|
||||
<ScopeSection
|
||||
description="access to the organization and all its members."
|
||||
hasReadScope={scopes.includes(OAuthScope.ORGANIZATIONS_READ)}
|
||||
hasWriteScope={scopes.includes(OAuthScope.ORGANIZATIONS_WRITE)}
|
||||
/>
|
||||
<ScopeSection
|
||||
description="access to metadata, its upgrade status, network restrictions and network bans."
|
||||
hasReadScope={scopes.includes(OAuthScope.PROJECTS_READ)}
|
||||
hasWriteScope={scopes.includes(OAuthScope.PROJECTS_WRITE)}
|
||||
/>
|
||||
<ScopeSection
|
||||
description="access to PostgREST configurations."
|
||||
hasReadScope={scopes.includes(OAuthScope.REST_READ)}
|
||||
hasWriteScope={scopes.includes(OAuthScope.REST_WRITE)}
|
||||
/>
|
||||
<ScopeSection
|
||||
description="access to API keys, secrets and pgsodium configurations."
|
||||
hasReadScope={scopes.includes(OAuthScope.SECRETS_READ)}
|
||||
hasWriteScope={scopes.includes(OAuthScope.SECRETS_WRITE)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-foreground-light">
|
||||
{name} ({domain}) is requesting API access to an organization. The application will be able
|
||||
to{' '}
|
||||
<span className="text-amber-1200">
|
||||
read and write the organization's settings and all of its projects.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import { OAuthScope } from '@supabase/shared-types/out/constants'
|
||||
import { Dispatch, PropsWithChildren, SetStateAction } from 'react'
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
IconChevronDown,
|
||||
} from 'ui'
|
||||
|
||||
const ScopeDropdownCheckboxItem = ({
|
||||
children,
|
||||
scopeName,
|
||||
scopes,
|
||||
onChange,
|
||||
}: PropsWithChildren<{
|
||||
scopeName: OAuthScope
|
||||
scopes: OAuthScope[]
|
||||
onChange: (v: OAuthScope[]) => void
|
||||
}>) => {
|
||||
return (
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={scopes.includes(scopeName)}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
onChange([...scopes, scopeName])
|
||||
} else {
|
||||
onChange([...scopes.filter((s) => s !== scopeName)])
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</DropdownMenuCheckboxItem>
|
||||
)
|
||||
}
|
||||
|
||||
const Scope = ({
|
||||
title,
|
||||
description,
|
||||
readScopeName,
|
||||
writeScopeName,
|
||||
scopes,
|
||||
setScopes,
|
||||
}: {
|
||||
title: string
|
||||
description: string
|
||||
readScopeName: OAuthScope
|
||||
writeScopeName: OAuthScope
|
||||
scopes: OAuthScope[]
|
||||
setScopes: Dispatch<SetStateAction<OAuthScope[]>>
|
||||
}) => {
|
||||
let accessDescription = 'Access: No access'
|
||||
if (scopes.includes(readScopeName)) {
|
||||
accessDescription = 'Access: Read-only'
|
||||
}
|
||||
if (scopes.includes(writeScopeName)) {
|
||||
accessDescription = 'Access: Write-only'
|
||||
}
|
||||
if (scopes.includes(readScopeName) && scopes.includes(writeScopeName)) {
|
||||
accessDescription = 'Access: Read and write'
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row justify-between p-4 border border-scale-300 dark:border-scale-500 first:rounded-t last:rounded-b"
|
||||
key={title}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-foreground text-sm">{title}</span>
|
||||
<span className="text-foreground-light text-xs">{description}</span>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<Button type="default" iconRight={<IconChevronDown />}>
|
||||
<p>{accessDescription}</p>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Select an access level</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<ScopeDropdownCheckboxItem scopeName={readScopeName} scopes={scopes} onChange={setScopes}>
|
||||
Read
|
||||
</ScopeDropdownCheckboxItem>
|
||||
<ScopeDropdownCheckboxItem
|
||||
scopeName={writeScopeName}
|
||||
scopes={scopes}
|
||||
onChange={setScopes}
|
||||
>
|
||||
Write
|
||||
</ScopeDropdownCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ScopesPanel = ({
|
||||
scopes,
|
||||
setScopes,
|
||||
}: {
|
||||
scopes: OAuthScope[]
|
||||
setScopes: Dispatch<SetStateAction<OAuthScope[]>>
|
||||
}) => {
|
||||
return (
|
||||
<div className="-space-y-px">
|
||||
<Scope
|
||||
title="Auth"
|
||||
description="Auth configurations and SSO providers."
|
||||
readScopeName={OAuthScope.AUTH_READ}
|
||||
writeScopeName={OAuthScope.AUTH_WRITE}
|
||||
scopes={scopes}
|
||||
setScopes={setScopes}
|
||||
/>
|
||||
<Scope
|
||||
title="Database"
|
||||
description="Postgres configurations, SQL snippets, SSL enforcement configurations and Typescript schema types."
|
||||
readScopeName={OAuthScope.DATABASE_READ}
|
||||
writeScopeName={OAuthScope.DATABASE_WRITE}
|
||||
scopes={scopes}
|
||||
setScopes={setScopes}
|
||||
/>
|
||||
<Scope
|
||||
title="Domains"
|
||||
description="Custom domains and vanity subdomains."
|
||||
readScopeName={OAuthScope.DOMAINS_READ}
|
||||
writeScopeName={OAuthScope.DOMAINS_WRITE}
|
||||
scopes={scopes}
|
||||
setScopes={setScopes}
|
||||
/>
|
||||
<Scope
|
||||
title="Edge Functions"
|
||||
description="Edge functions."
|
||||
readScopeName={OAuthScope.EDGE_FUNCTIONS_READ}
|
||||
writeScopeName={OAuthScope.EDGE_FUNCTIONS_WRITE}
|
||||
scopes={scopes}
|
||||
setScopes={setScopes}
|
||||
/>
|
||||
<Scope
|
||||
title="Environment"
|
||||
description="Environments/branches."
|
||||
readScopeName={OAuthScope.ENVIRONMENT_READ}
|
||||
writeScopeName={OAuthScope.ENVIRONMENT_WRITE}
|
||||
scopes={scopes}
|
||||
setScopes={setScopes}
|
||||
/>
|
||||
<Scope
|
||||
title="Organizations"
|
||||
description="Organizations and all its members."
|
||||
readScopeName={OAuthScope.ORGANIZATIONS_READ}
|
||||
writeScopeName={OAuthScope.ORGANIZATIONS_WRITE}
|
||||
scopes={scopes}
|
||||
setScopes={setScopes}
|
||||
/>
|
||||
<Scope
|
||||
title="Projects"
|
||||
description="Metadata, upgrade status, network restrictions and network bans."
|
||||
readScopeName={OAuthScope.PROJECTS_READ}
|
||||
writeScopeName={OAuthScope.PROJECTS_WRITE}
|
||||
scopes={scopes}
|
||||
setScopes={setScopes}
|
||||
/>
|
||||
<Scope
|
||||
title="REST"
|
||||
description="PostgREST configurations."
|
||||
readScopeName={OAuthScope.REST_READ}
|
||||
writeScopeName={OAuthScope.REST_WRITE}
|
||||
scopes={scopes}
|
||||
setScopes={setScopes}
|
||||
/>
|
||||
<Scope
|
||||
title="Secrets"
|
||||
description="API keys, secrets and pgsodium configurations."
|
||||
readScopeName={OAuthScope.SECRETS_READ}
|
||||
writeScopeName={OAuthScope.SECRETS_WRITE}
|
||||
scopes={scopes}
|
||||
setScopes={setScopes}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+36
-2
@@ -1,6 +1,7 @@
|
||||
import clsx from 'clsx'
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { OAuthScope } from '@supabase/shared-types/out/constants'
|
||||
import { useParams } from 'common'
|
||||
import {
|
||||
OAuthAppCreateResponse,
|
||||
@@ -20,12 +21,15 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
Form,
|
||||
IconEdit,
|
||||
IconExternalLink,
|
||||
IconUpload,
|
||||
Input,
|
||||
Modal,
|
||||
SidePanel,
|
||||
} from 'ui'
|
||||
import AuthorizeRequesterDetails from './AuthorizeRequesterDetails'
|
||||
import AuthorizeRequesterDetails from '../AuthorizeRequesterDetails'
|
||||
import { ScopesPanel } from './Scopes'
|
||||
import Link from 'next/link'
|
||||
|
||||
export interface PublishAppSidePanelProps {
|
||||
visible: boolean
|
||||
@@ -88,6 +92,7 @@ const PublishAppSidePanel = ({
|
||||
const [iconUrl, setIconUrl] = useState<string>()
|
||||
const [errors, setErrors] = useState<{ [key: string]: string }>({})
|
||||
const [urls, setUrls] = useState<{ id: string; value: string }[]>([{ id: uuidv4(), value: '' }])
|
||||
const [scopes, setScopes] = useState<OAuthScope[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
@@ -101,9 +106,11 @@ const PublishAppSidePanel = ({
|
||||
return { id: uuidv4(), value: url }
|
||||
})
|
||||
)
|
||||
setScopes(selectedApp.scopes)
|
||||
setIconUrl(selectedApp.icon === null ? undefined : selectedApp.icon)
|
||||
} else {
|
||||
setUrls([{ id: uuidv4(), value: '' }])
|
||||
setScopes([])
|
||||
setIconUrl(undefined)
|
||||
}
|
||||
}
|
||||
@@ -164,6 +171,7 @@ const PublishAppSidePanel = ({
|
||||
name,
|
||||
website,
|
||||
redirect_uris,
|
||||
scopes,
|
||||
icon: uploadedIconUrl,
|
||||
})
|
||||
} else {
|
||||
@@ -174,6 +182,7 @@ const PublishAppSidePanel = ({
|
||||
name,
|
||||
website,
|
||||
redirect_uris,
|
||||
scopes,
|
||||
icon: uploadedIconUrl === undefined ? null : uploadedIconUrl,
|
||||
})
|
||||
}
|
||||
@@ -332,6 +341,30 @@ const PublishAppSidePanel = ({
|
||||
</div>
|
||||
</SidePanel.Content>
|
||||
<SidePanel.Separator />
|
||||
<div className="p-6 ">
|
||||
<div className="flex items-start justify-between space-x-4 pb-4">
|
||||
<div className="flex flex-col">
|
||||
<span className="prose text-sm">Application permissions</span>
|
||||
<span className="text-sm text-foreground-light">
|
||||
The application permissions are organized in scopes and will be
|
||||
presented to the user when adding an app to their organization and all
|
||||
of its projects.
|
||||
</span>
|
||||
</div>
|
||||
<Link
|
||||
passHref
|
||||
href="https://supabase.com/docs/guides/platform/oauth-apps/oauth-scopes"
|
||||
>
|
||||
<Button asChild type="default" icon={<IconExternalLink />}>
|
||||
<a target="_blank" rel="noreferrer">
|
||||
Documentation
|
||||
</a>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<ScopesPanel scopes={scopes} setScopes={setScopes} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SidePanel.Separator />
|
||||
@@ -359,7 +392,7 @@ const PublishAppSidePanel = ({
|
||||
|
||||
<Modal
|
||||
hideFooter
|
||||
className="!w-[500px]"
|
||||
className="!max-w-[600px]"
|
||||
visible={showPreview}
|
||||
onCancel={() => setShowPreview(false)}
|
||||
>
|
||||
@@ -376,6 +409,7 @@ const PublishAppSidePanel = ({
|
||||
icon={iconUrl || null}
|
||||
name={values.name}
|
||||
domain={values.website}
|
||||
scopes={scopes}
|
||||
/>
|
||||
<div className="pt-4 space-y-2">
|
||||
<p className="prose text-sm">
|
||||
@@ -1,9 +1,9 @@
|
||||
import Divider from 'components/ui/Divider'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import { useTheme } from 'next-themes'
|
||||
import Head from 'next/head'
|
||||
import Image from 'next/image'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import Divider from 'components/ui/Divider'
|
||||
|
||||
export interface APIAuthorizationLayoutProps {}
|
||||
|
||||
@@ -37,7 +37,7 @@ const APIAuthorizationLayout = ({ children }: PropsWithChildren<APIAuthorization
|
||||
</div>
|
||||
</div>
|
||||
<Divider light />
|
||||
<div className="flex flex-col justify-center flex-grow mx-auto max-w-[90vw] md:max-w-xl h-full space-y-4">
|
||||
<div className="flex flex-col justify-center flex-grow mx-auto w-[90vw] max-w-[600px] h-full space-y-4">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OAuthScope } from '@supabase/shared-types/out/constants'
|
||||
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_ADMIN_URL } from 'lib/constants'
|
||||
@@ -13,6 +14,7 @@ export type ApiAuthorizationResponse = {
|
||||
website: string
|
||||
icon: string | null
|
||||
domain: string
|
||||
scopes: OAuthScope[]
|
||||
expires_at: string
|
||||
approved_at: string | null
|
||||
approved_organization_slug?: string
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { OAuthScope } from '@supabase/shared-types/out/constants'
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_ADMIN_URL } from 'lib/constants'
|
||||
import { oauthAppKeys } from './keys'
|
||||
import { ResponseError } from 'types'
|
||||
import { oauthAppKeys } from './keys'
|
||||
|
||||
export type OAuthAppCreateVariables = {
|
||||
slug: string
|
||||
name: string
|
||||
website: string
|
||||
icon?: string
|
||||
scopes?: OAuthScope[]
|
||||
redirect_uris: string[]
|
||||
}
|
||||
|
||||
@@ -25,12 +27,14 @@ export async function createOAuthApp({
|
||||
name,
|
||||
website,
|
||||
icon,
|
||||
scopes,
|
||||
redirect_uris,
|
||||
}: OAuthAppCreateVariables) {
|
||||
const response = await post(`${API_ADMIN_URL}/organizations/${slug}/oauth/apps`, {
|
||||
name,
|
||||
website,
|
||||
icon,
|
||||
scopes,
|
||||
redirect_uris,
|
||||
})
|
||||
if (response.error) throw response.error
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OAuthScope } from '@supabase/shared-types/out/constants'
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
@@ -12,6 +13,7 @@ export type OAuthAppUpdateVariables = {
|
||||
name: string
|
||||
website: string
|
||||
icon?: string | null
|
||||
scopes?: OAuthScope[]
|
||||
redirect_uris: string[]
|
||||
}
|
||||
|
||||
@@ -21,6 +23,7 @@ export async function updateOAuthApp({
|
||||
name,
|
||||
website,
|
||||
icon,
|
||||
scopes,
|
||||
redirect_uris,
|
||||
}: OAuthAppUpdateVariables) {
|
||||
if (!id) throw new Error('OAuth app ID is required')
|
||||
@@ -33,6 +36,7 @@ export async function updateOAuthApp({
|
||||
name,
|
||||
website,
|
||||
icon,
|
||||
scopes,
|
||||
redirect_uris,
|
||||
})
|
||||
if (response.error) throw response.error
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { OAuthScope } from '@supabase/shared-types/out/constants'
|
||||
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_ADMIN_URL } from 'lib/constants'
|
||||
import { useCallback } from 'react'
|
||||
import { oauthAppKeys } from './keys'
|
||||
import { ResponseError } from 'types'
|
||||
import { oauthAppKeys } from './keys'
|
||||
|
||||
export type OAuthAppsVariables = {
|
||||
slug?: string
|
||||
@@ -17,6 +18,7 @@ export type OAuthApp = {
|
||||
created_at: string
|
||||
name: string
|
||||
website: string
|
||||
scopes: OAuthScope[]
|
||||
redirect_uris: string[]
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -7,7 +7,6 @@
|
||||
"dev:secrets:pull": "AWS_PROFILE=supabase-dev node internals/getSecrets.js",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"test": "jest",
|
||||
"lint": "next lint",
|
||||
"deploy:staging": "VERCEL_ORG_ID=team_E6KJ1W561hMTjon1QSwOh0WO VERCEL_PROJECT_ID=QmcmhbiAtCMFTAHCuGgQscNbke4TzgWULECctNcKmxWCoT vercel --prod -A .vercel/staging.json",
|
||||
"typecheck": "tsc --noEmit",
|
||||
@@ -36,7 +35,7 @@
|
||||
"@supabase/auth-helpers-nextjs": "^0.5.6",
|
||||
"@supabase/auth-helpers-react": "^0.3.1",
|
||||
"@supabase/react-data-grid": "^7.1.0-beta.7",
|
||||
"@supabase/shared-types": "0.1.47",
|
||||
"@supabase/shared-types": "0.1.51",
|
||||
"@supabase/supabase-js": "^2.23.0",
|
||||
"@tanstack/react-query": "^4.22.0",
|
||||
"@tanstack/react-query-devtools": "^4.22.0",
|
||||
|
||||
@@ -158,12 +158,13 @@ const APIAuthorizationPage: NextPageWithLayout = () => {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="w-full md:w-[500px] px-8 py-6 space-y-8">
|
||||
<div className="w-full px-8 py-6 space-y-8">
|
||||
{/* API Authorization requester details */}
|
||||
<AuthorizeRequesterDetails
|
||||
icon={requester.icon}
|
||||
name={requester.name}
|
||||
domain={requester.domain}
|
||||
scopes={requester.scopes}
|
||||
/>
|
||||
|
||||
{/* Expiry warning */}
|
||||
|
||||
Reference in new issue
Block a user