Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 25 additions & 11 deletions apps/docs/content/guides/auth/auth-email-templates.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,34 @@ For mobile applications, you might need to link or redirect to a specific page w

Certain email providers may have spam detection or other security features that prefetch URL links from incoming emails (e.g. [Safe Links in Microsoft Defender for Office 365](https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/safe-links-about?view=o365-worldwide)).
In this scenario, the `{{ .ConfirmationURL }}` sent will be consumed instantly which leads to a "Token has expired or is invalid" error.
To guard against this:
To guard against this there are the options below:

- Use an email OTP instead by including `{{ .Token }}` in the email template.
- Create your own custom email link to redirect the user to a page where they can click on a button to confirm the action.
For example, you can include the following in your email template:
**Option 1**

```html
<a href="{{ .SiteURL }}/confirm-signup?confirmation_url={{ .ConfirmationURL }}"
>Confirm your signup
</a>
```
- Use an email OTP instead by including `{{ .Token }}` in the email template
- Create your own custom email link to redirect the user to a page where they can enter with their email and token to login

The user should be brought to a page on your site where they can confirm the action by clicking a button.
The button should contain the actual confirmation link which can be obtained from parsing the `confirmation_url={{ .ConfirmationURL }}` query parameter in the URL.
```html
<a href="{{ .SiteURL }}/confirm-signup">Confirm your signup</a>
```

- Log them in by verifying the OTP token value with their email e.g. with [`supabase.auth.verifyOtp`](/docs/reference/javascript/auth-verifyotp) show below

```ts
const { data, error } = await supabase.auth.verifyOtp({ email, token, type: 'email' })
```

**Option 2**

- Create your own custom email link to redirect the user to a page where they can click on a button to confirm the action

```html
<a href="{{ .SiteURL }}/confirm-signup?confirmation_url={{ .ConfirmationURL }}"
>Confirm your signup</a
>
```

- The button should contain the actual confirmation link which can be obtained from parsing the `confirmation_url={{ .ConfirmationURL }}` query parameter in the URL.

### Email tracking

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { PostgresTrigger } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { includes, sortBy } from 'lodash'
import { Check, Copy, Edit, Edit2, MoreVertical, Trash, X } from 'lucide-react'

import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useParams } from 'common'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { InlineLink } from 'components/ui/InlineLink'
import { useDatabaseTriggersQuery } from 'data/database-triggers/database-triggers-query'
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
Expand All @@ -24,7 +27,6 @@ import {
TooltipTrigger,
} from 'ui'
import { generateTriggerCreateSQL } from './TriggerList.utils'
import { PostgresTrigger } from '@supabase/postgres-meta'

interface TriggerListProps {
schema: string
Expand All @@ -35,18 +37,24 @@ interface TriggerListProps {
deleteTrigger: (trigger: PostgresTrigger) => void
}

const TriggerList = ({
export const TriggerList = ({
schema,
filterString,
isLocked,
editTrigger,
duplicateTrigger,
deleteTrigger,
}: TriggerListProps) => {
const { ref: projectRef } = useParams()
const { data: project } = useSelectedProjectQuery()
const aiSnap = useAiAssistantStateSnapshot()
const { openSidebar } = useSidebarManagerSnapshot()

const { can: canUpdateTriggers } = useAsyncCheckPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'triggers'
)

const { data: triggers } = useDatabaseTriggersQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
Expand All @@ -56,15 +64,10 @@ const TriggerList = ({
includes(x.name.toLowerCase(), filterString.toLowerCase()) ||
(x.function_name && includes(x.function_name.toLowerCase(), filterString.toLowerCase()))
)

const _triggers = sortBy(
filteredTriggers.filter((x) => x.schema == schema),
(trigger) => trigger.name.toLocaleLowerCase()
)
const { can: canUpdateTriggers } = useAsyncCheckPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'triggers'
)

if (_triggers.length === 0 && filterString.length === 0) {
return (
Expand Down Expand Up @@ -94,7 +97,7 @@ const TriggerList = ({

return (
<>
{_triggers.map((x: any) => (
{_triggers.map((x) => (
<TableRow key={x.id}>
<TableCell className="space-x-2">
<Tooltip>
Expand All @@ -111,15 +114,33 @@ const TriggerList = ({
</TableCell>

<TableCell className="break-all">
<p title={x.table} className="truncate">
{x.table}
</p>
{x.table_id ? (
<InlineLink
title={x.table}
href={`/project/${projectRef}/editor/${x.table_id}`}
className="truncate block max-w-40"
>
{x.table}
</InlineLink>
) : (
<p title={x.table} className="truncate">
{x.table}
</p>
)}
</TableCell>

<TableCell className="space-x-2">
<p title={x.function_name} className="truncate">
{x.function_name}
</p>
{x.function_name ? (
<InlineLink
title={x.function_name}
href={`/project/${projectRef}/database/functions?search=${x.function_name}&schema=${x.function_schema}`}
className="truncate block max-w-40"
>
{x.function_name}
</InlineLink>
) : (
<p className="truncate text-foreground-light">-</p>
)}
</TableCell>

<TableCell>
Expand Down Expand Up @@ -176,7 +197,7 @@ const TriggerList = ({
const sql = generateTriggerCreateSQL(x)
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
aiSnap.newChat({
name: `Update trigger ${X.name}`,
name: `Update trigger ${x.name}`,
initialInput: `Update this trigger which exists on the ${x.schema}.${x.table} table to...`,
suggestions: {
title:
Expand Down Expand Up @@ -237,5 +258,3 @@ const TriggerList = ({
</>
)
}

export default TriggerList
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import { noop } from 'lodash'
import { DatabaseZap, FunctionSquare, Plus, Search, Shield } from 'lucide-react'
import { parseAsString, useQueryState } from 'nuqs'

import AlphaPreview from 'components/to-be-cleaned/AlphaPreview'
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import AlertError from 'components/ui/AlertError'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import SchemaSelector from 'components/ui/SchemaSelector'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { useDatabaseTriggersQuery } from 'data/database-triggers/database-triggers-query'
import { useTablesQuery } from 'data/tables/tables-query'
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
Expand All @@ -21,10 +19,7 @@ import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
import {
AiIconAnimation,
Button,
Card,
CardContent,
cn,
Input,
Table,
TableBody,
Expand All @@ -33,8 +28,7 @@ import {
TableRow,
} from 'ui'
import { ProtectedSchemaWarning } from '../../ProtectedSchemaWarning'
import TriggerList from './TriggerList'
import Link from 'next/link'
import { TriggerList } from './TriggerList'

interface TriggersListProps {
createTrigger: () => void
Expand All @@ -43,7 +37,7 @@ interface TriggersListProps {
deleteTrigger: (trigger: PostgresTrigger) => void
}

const TriggersList = ({
export const TriggersList = ({
createTrigger = noop,
editTrigger = noop,
duplicateTrigger = noop,
Expand Down Expand Up @@ -248,5 +242,3 @@ const TriggersList = ({
</div>
)
}

export default TriggersList
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const ModelSelector = ({ selectedModel, onSelectModel }: ModelSelectorPro
<Popover_Shadcn_ open={open} onOpenChange={setOpen}>
<PopoverTrigger_Shadcn_ asChild>
<Button
type="outline"
type="default"
className="text-foreground-light"
iconRight={<ChevronsUpDown strokeWidth={1} size={12} />}
>
Expand Down
13 changes: 11 additions & 2 deletions apps/studio/components/ui/InlineLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface InlineLinkProps {
className?: string
target?: string
rel?: string
title?: string
onClick?: () => void
}

Expand All @@ -17,18 +18,26 @@ export const InlineLink = ({
href,
className: _className,
children,
title,
...props
}: PropsWithChildren<InlineLinkProps>) => {
const className = cn(InlineLinkClassName, _className)
if (href.startsWith('http')) {
return (
<a className={className} href={href} target="_blank" rel="noreferrer noopener" {...props}>
<a
title={title}
className={className}
href={href}
target="_blank"
rel="noreferrer noopener"
{...props}
>
{children}
</a>
)
}
return (
<Link className={className} href={href} {...props}>
<Link className={className} href={href} title={title} {...props}>
{children}
</Link>
)
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/pages/project/[ref]/database/triggers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useIsInlineEditorEnabled } from 'components/interfaces/App/FeaturePrevi
import { DeleteTrigger } from 'components/interfaces/Database/Triggers/DeleteTrigger'
import { TriggerSheet } from 'components/interfaces/Database/Triggers/TriggerSheet'
import { generateTriggerCreateSQL } from 'components/interfaces/Database/Triggers/TriggersList/TriggerList.utils'
import TriggersList from 'components/interfaces/Database/Triggers/TriggersList/TriggersList'
import { TriggersList } from 'components/interfaces/Database/Triggers/TriggersList/TriggersList'
import DatabaseLayout from 'components/layouts/DatabaseLayout/DatabaseLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
Expand Down
Loading