Skip to content

Commit

Permalink
feat: add anonymous sign-ins config (#21813)
Browse files Browse the repository at this point in the history
* feat: add anonymous sign-ins config

* fix: update anonymous sign-ins toggle & policy editor

* Couple of UX changes

* fix: update user impersonation with is_anonymous claim

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
  • Loading branch information
kangmingtay and joshenlim committed Mar 27, 2024
1 parent 74455f6 commit 0a569e4
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutati
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions, useSelectedOrganization } from 'hooks'
import { IS_PLATFORM } from 'lib/constants'
import Link from 'next/link'
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
import FormField from '../AuthProvidersForm/FormField'

// Use a const string to represent no chars option. Represented as empty string on the backend side.
Expand All @@ -41,6 +43,7 @@ const LOWER_UPPER_DIGITS_SYMBOLS = LOWER_UPPER_DIGITS + ':!@#$%^&*()_+-=[]{};\'\

const schema = object({
DISABLE_SIGNUP: boolean().required(),
EXTERNAL_ANONYMOUS_USERS_ENABLED: boolean().required(),
SECURITY_MANUAL_LINKING_ENABLED: boolean().required(),
SITE_URL: string().required('Must have a Site URL'),
SECURITY_CAPTCHA_ENABLED: boolean().required(),
Expand Down Expand Up @@ -102,6 +105,7 @@ const BasicAuthSettingsForm = () => {

const INITIAL_VALUES = {
DISABLE_SIGNUP: !authConfig?.DISABLE_SIGNUP,
EXTERNAL_ANONYMOUS_USERS_ENABLED: authConfig?.EXTERNAL_ANONYMOUS_USERS_ENABLED,
SECURITY_MANUAL_LINKING_ENABLED: authConfig?.SECURITY_MANUAL_LINKING_ENABLED || false,
SITE_URL: authConfig?.SITE_URL,
SECURITY_CAPTCHA_ENABLED: authConfig?.SECURITY_CAPTCHA_ENABLED || false,
Expand Down Expand Up @@ -203,6 +207,71 @@ const BasicAuthSettingsForm = () => {
}
disabled={!canUpdateConfig}
/>
<Toggle
id="EXTERNAL_ANONYMOUS_USERS_ENABLED"
size="small"
label="Allow anonymous sign-ins"
layout="flex"
descriptionText={
<Markdown
extLinks
className="[&>p>a]:text-foreground-light [&>p>a]:transition-all [&>p>a]:hover:text-foreground [&>p>a]:hover:decoration-brand"
content="Enable [anonymous sign-ins](https://supabase.com/docs/guides/auth/auth-anonymous) for your project."
/>
}
disabled={!canUpdateConfig}
/>
{values.EXTERNAL_ANONYMOUS_USERS_ENABLED && (
<div className="flex flex-col gap-y-2">
<Alert_Shadcn_
className="flex w-full items-center justify-between"
variant="warning"
>
<WarningIcon />
<div>
<AlertTitle_Shadcn_>
Anonymous users will use the{' '}
<code className="text-xs">authenticated</code> role when signing in
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
As a result, anonymous users will be subjected to RLS policies that
apply to the <code className="text-xs">public</code> and{' '}
<code className="text-xs">authenticated</code> roles. We strongly advise{' '}
<Link
href={`/project/${projectRef}/auth/policies`}
className="text-foreground underline"
>
reviewing your RLS policies
</Link>{' '}
to ensure that access to your data is restricted where required.
</AlertDescription_Shadcn_>
</div>
</Alert_Shadcn_>
{!values.SECURITY_CAPTCHA_ENABLED && (
<Alert_Shadcn_>
<WarningIcon />
<AlertTitle_Shadcn_>
We highly recommend{' '}
<span
tabIndex={1}
className="cursor-pointer underline"
onClick={() => {
const el = document.getElementById('enable-captcha')
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' })
}}
>
enabling captcha
</span>{' '}
for anonymous sign-ins
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
This will prevent potential abuse on sign-ins which may bloat your
database and incur costs for monthly active users (MAU)
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
</div>
)}
</FormSectionContent>
</FormSection>
<FormSection header={<FormSectionLabel>Passwords</FormSectionLabel>}>
Expand Down Expand Up @@ -310,7 +379,10 @@ const BasicAuthSettingsForm = () => {
/>
</FormSectionContent>
</FormSection>
<FormSection header={<FormSectionLabel>Bot and Abuse Protection</FormSectionLabel>}>
<FormSection
id="enable-captcha"
header={<FormSectionLabel>Bot and Abuse Protection</FormSectionLabel>}
>
<FormSectionContent loading={isLoading}>
<Toggle
id="SECURITY_CAPTCHA_ENABLED"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { PostgresPolicy } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { noop } from 'lodash'
import {
Badge,
Button,
DropdownMenu,
DropdownMenuContent,
Expand All @@ -16,6 +17,8 @@ import {

import Panel from 'components/ui/Panel'
import { useCheckPermissions } from 'hooks'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { useAuthConfigQuery } from 'data/auth/auth-config-query'

interface PolicyRowProps {
policy: PostgresPolicy
Expand All @@ -30,6 +33,15 @@ const PolicyRow = ({
}: PolicyRowProps) => {
const canUpdatePolicies = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'policies')

const { project } = useProjectContext()
const { data: authConfig } = useAuthConfigQuery({ projectRef: project?.ref })

// TODO(km): Simple check for roles that allow authenticated access.
// In the future, we'll use splinter to return proper warnings for policies that allow anonymous user access.
const appliesToAnonymousUsers =
authConfig?.EXTERNAL_ANONYMOUS_USERS_ENABLED &&
(policy.roles.includes('authenticated') || policy.roles.includes('public'))

return (
<Panel.Content
className={['flex border-overlay', 'w-full space-x-4 border-b py-4 lg:items-center'].join(
Expand All @@ -40,6 +52,9 @@ const PolicyRow = ({
<div className="flex items-center space-x-4">
<p className="font-mono text-xs text-foreground-light">{policy.command}</p>
<p className="text-sm text-foreground">{policy.name}</p>
{appliesToAnonymousUsers ? (
<Badge color="yellow">Applies to anonymous users</Badge>
) : null}
</div>
<div className="flex items-center space-x-2">
<p className="text-foreground-light text-sm">Applied to:</p>
Expand Down
130 changes: 93 additions & 37 deletions apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ import {
FormControl_Shadcn_,
FormField_Shadcn_,
FormItem_Shadcn_,
FormMessage_Shadcn_,
Form_Shadcn_,
IconAlertCircle,
Input_Shadcn_,
} from 'ui'
import * as z from 'zod'
Expand All @@ -32,8 +30,9 @@ import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { useAuthConfigQuery } from 'data/auth/auth-config-query'
import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation'
import { useCheckPermissions } from 'hooks'
import { isSmtpEnabled } from '../SmtpForm/SmtpForm.utils'
import toast from 'react-hot-toast'
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
import { isSmtpEnabled } from '../SmtpForm/SmtpForm.utils'

const RateLimits = () => {
const formId = 'auth-rate-limits-form'
Expand All @@ -58,6 +57,7 @@ const RateLimits = () => {

const canUpdateEmailLimit = authConfig?.EXTERNAL_EMAIL_ENABLED && isSmtpEnabled(authConfig)
const canUpdateSMSRateLimit = authConfig?.EXTERNAL_PHONE_ENABLED && !authConfig?.SMS_AUTOCONFIRM
const canUpdateAnonymousUsersRateLimit = authConfig?.EXTERNAL_ANONYMOUS_USERS_ENABLED

const FormSchema = z.object({
RATE_LIMIT_TOKEN_REFRESH: z.coerce
Expand All @@ -76,6 +76,10 @@ const RateLimits = () => {
.number()
.min(0, 'Must be not be lower than 0')
.max(32767, 'Must not be more than 32,767 an hour'),
RATE_LIMIT_ANONYMOUS_USERS: z.coerce
.number()
.min(0, 'Must be not be lower than 0')
.max(32767, 'Must not be more than 32,767 an hour'),
})

const form = useForm<z.infer<typeof FormSchema>>({
Expand All @@ -97,6 +101,7 @@ const RateLimits = () => {
'RATE_LIMIT_VERIFY',
'RATE_LIMIT_EMAIL_SENT',
'RATE_LIMIT_SMS_SENT',
'RATE_LIMIT_ANONYMOUS_USERS',
] as (keyof typeof payload)[]
params.forEach((param) => {
if (data[param] !== authConfig?.[param]) payload[param] = data[param]
Expand All @@ -112,6 +117,7 @@ const RateLimits = () => {
RATE_LIMIT_VERIFY: authConfig.RATE_LIMIT_VERIFY,
RATE_LIMIT_EMAIL_SENT: authConfig.RATE_LIMIT_EMAIL_SENT,
RATE_LIMIT_SMS_SENT: authConfig.RATE_LIMIT_SMS_SENT,
RATE_LIMIT_ANONYMOUS_USERS: authConfig.RATE_LIMIT_ANONYMOUS_USERS,
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -173,39 +179,42 @@ const RateLimits = () => {
<FormControl_Shadcn_>
<Input_Shadcn_ disabled={!canUpdateEmailLimit} type="number" {...field} />
</FormControl_Shadcn_>
<FormMessage_Shadcn_ />
{!canUpdateEmailLimit && (
{!authConfig.EXTERNAL_EMAIL_ENABLED ? (
<Alert_Shadcn_>
<IconAlertCircle strokeWidth={1.5} />
<WarningIcon />
<AlertTitle_Shadcn_>
{!authConfig.EXTERNAL_EMAIL_ENABLED &&
'Enable email-based logins to update this configuration'}
{!isSmtpEnabled(authConfig) &&
'Custom SMTP provider is required to update this configuration'}
Email-based logins are not enabled for your project
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
<AlertDescription_Shadcn_ className="flex flex-col gap-y-3">
<p className="!leading-tight">
{!authConfig.EXTERNAL_EMAIL_ENABLED &&
'Head over to the providers page to enable email provider before updating your rate limit'}
{!isSmtpEnabled(authConfig) &&
'The built-in email service has a fixed rate limit. You will need to set up your own custom SMTP provider to update your email rate limit'}
Enable email-based logins to update this rate limit
</p>
<Button asChild type="default" className="mt-2">
<Link
href={
!authConfig.EXTERNAL_EMAIL_ENABLED
? `/project/${projectRef}/auth/providers`
: `/project/${projectRef}/settings/auth`
}
>
{!authConfig.EXTERNAL_EMAIL_ENABLED
? 'View providers configuration'
: 'View SMTP settings'}
<Button asChild type="default" className="w-min">
<Link href={`/project/${projectRef}/auth/providers`}>
View auth providers
</Link>
</Button>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
) : !isSmtpEnabled(authConfig) ? (
<Alert_Shadcn_>
<WarningIcon />
<AlertTitle_Shadcn_>
Custom SMTP provider is required to update this configuration
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_ className="flex flex-col gap-y-3">
<p className="!leading-tight">
The built-in email service has a fixed rate limit. You will need to
set up your own custom SMTP provider to update your email rate limit
</p>
<Button asChild type="default" className="w-min">
<Link href={`/project/${projectRef}/settings/auth`}>
View SMTP settings
</Link>
</Button>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
) : null}
</FormItem_Shadcn_>
)}
/>
Expand Down Expand Up @@ -239,21 +248,19 @@ const RateLimits = () => {
{...field}
/>
</FormControl_Shadcn_>
<FormMessage_Shadcn_ />
{!canUpdateSMSRateLimit && (
<Alert_Shadcn_>
<IconAlertCircle strokeWidth={1.5} />
<WarningIcon />
<AlertTitle_Shadcn_>
Enable phone-based logins to update this configuration
Phone-based logins are not enabled for your project
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
<AlertDescription_Shadcn_ className="flex flex-col gap-y-3">
<p className="!leading-tight">
Head over to the providers page to enable phone provider and phone
confirmations before updating your rate limit
Enable phone-based logins to update this rate limit
</p>
<Button asChild type="default" className="mt-2">
<Button asChild type="default" className="w-min">
<Link href={`/project/${projectRef}/auth/providers`}>
View providers configuration
View auth providers
</Link>
</Button>
</AlertDescription_Shadcn_>
Expand Down Expand Up @@ -295,7 +302,6 @@ const RateLimits = () => {
</p>
</>
)}
<FormMessage_Shadcn_ />
</FormItem_Shadcn_>
)}
/>
Expand Down Expand Up @@ -331,7 +337,57 @@ const RateLimits = () => {
This is equivalent to {field.value * 12} requests per hour
</p>
)}
<FormMessage_Shadcn_ />
</FormItem_Shadcn_>
)}
/>
</FormSectionContent>
</FormSection>

<FormSection
id="anonymous-users"
header={
<FormSectionLabel
description={
<p className="text-foreground-light text-sm">
Number of anonymous sign-ins that can be made per hour
</p>
}
>
Rate limit for anonymous users
</FormSectionLabel>
}
>
<FormSectionContent loading={false}>
<FormField_Shadcn_
control={form.control}
name="RATE_LIMIT_ANONYMOUS_USERS"
render={({ field }) => (
<FormItem_Shadcn_>
<FormControl_Shadcn_>
<Input_Shadcn_
disabled={!canUpdateAnonymousUsersRateLimit}
type="number"
{...field}
/>
</FormControl_Shadcn_>
{!canUpdateAnonymousUsersRateLimit && (
<Alert_Shadcn_>
<WarningIcon />
<AlertTitle_Shadcn_>
Anonymous logins are not enabled for your project
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_ className="flex flex-col gap-y-3">
<p className="!leading-tight">
Enable anonymous logins to update this rate limit
</p>
<Button asChild type="default" className="w-min">
<Link href={`/project/${projectRef}/settings/auth`}>
View auth settings
</Link>
</Button>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
</FormItem_Shadcn_>
)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ interface UserRowProps {

const UserImpersonatingRow = ({ user, onClick, isImpersonating = false }: UserRowProps) => {
const avatarUrl = getAvatarUrl(user)
const displayName = getDisplayName(user, user.email ?? user.phone ?? user.id ?? 'Unknown')
const displayName =
getDisplayName(user, user.email ?? user.phone ?? user.id ?? 'Unknown') +
(user.is_anonymous ? ' (anonymous)' : '')

return (
<div className="flex items-center gap-3 py-2 text-foreground">
Expand Down Expand Up @@ -173,7 +175,9 @@ interface UserRowProps {

const UserRow = ({ user, onClick, isImpersonating = false }: UserRowProps) => {
const avatarUrl = getAvatarUrl(user)
const displayName = getDisplayName(user, user.email ?? user.phone ?? user.id ?? 'Unknown')
const displayName =
getDisplayName(user, user.email ?? user.phone ?? user.id ?? 'Unknown') +
(user.is_anonymous ? ' (anonymous)' : '')

return (
<div className="flex items-center justify-between py-2 text-foreground">
Expand Down
1 change: 1 addition & 0 deletions apps/studio/lib/role-impersonation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function getPostgrestClaims(projectRef: string, role: PostgrestImpersonationRole
session_id: uuidv4(),
sub: user.id,
user_metadata: user.raw_user_meta_data,
is_anonymous: user.is_anonymous,
}
}

Expand Down
Loading

0 comments on commit 0a569e4

Please sign in to comment.